ref: 28e8ccb5203de0d122310d70e5751c9778adbbaf
parent: 8b24786f2b7a6faf246874b4e50149c35628e2b8
author: jgstratton <jgstratton@cyber.lan>
date: Sun Oct 3 11:29:56 EDT 2021
Initial Add
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
# plan9-acme-themes
-acme, but it uses themes from rio-themes if they are loaded. Otherwise, it will default to the traditional rio theme.
+This is plan’s acme text editor, but it will use themes from rio-themes (https://ftrv.se/14) if they are installed.
+
+Basically, this code is the functions from rio-themes, yanked out and dumped into acme. Currently it only reads the themes and execution. So if you change your theme, you will need to restart acme if you want it to reflect the new theme. If no theme is installed, then it defaults to traditional acme.
--- /dev/null
+++ b/acme.c
@@ -1,0 +1,1071 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+void themeload(char *s, int n);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = 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 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* 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 || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0)
+ readfile(c, wdir);
+ else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", user, 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);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ 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 *)
+{
+ 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];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ 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);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ 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->Mouse;
+ qlock(&row);
+ 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){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && 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);
+ 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 *)
+{
+ 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];
+
+ 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);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ 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);
+ 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\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ 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);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ 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);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+static char *
+readall(int f, int *osz)
+{
+ int bufsz, sz, n;
+ char *s;
+
+ bufsz = 1023;
+ s = nil;
+ for(sz = 0;; sz += n){
+ if(bufsz-sz < 1024){
+ bufsz *= 2;
+ s = realloc(s, bufsz);
+ }
+ if((n = readn(f, s+sz, bufsz-sz-1)) < 1)
+ break;
+ }
+ if(n < 0 || sz < 1){
+ free(s);
+ return nil;
+ }
+ s[sz] = 0;
+ *osz = sz;
+
+ return s;
+}
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* jgs3 - Apply the themes */
+ int f, sz;
+ char *s;
+ if((f = open("/dev/theme", OREAD|OCEXEC)) >= 0){
+ if((s = readall(f, &sz)) != nil)
+ themeload(s, sz);
+ free(s);
+ close(f);
+
+ /* Menu */
+ tagcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colmenuback].rgb<<8|0xff);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colmenuhigh].rgb<<8|0xff);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colmenubord].rgb<<8|0xff);
+ tagcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colmenutext].rgb<<8|0xff);
+ tagcols[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colmenuhtext].rgb<<8|0xff);
+
+ /* Body */
+ textcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colback].rgb<<8|0xff);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colhigh].rgb<<8|0xff);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colbord].rgb<<8|0xff);
+ textcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Coltext].rgb<<8|0xff);
+ textcols[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, theme[Colhtext].rgb<<8|0xff);
+
+ } else {
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+ }
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
+
+/* jgs1 - functions to load colors on startup */
+void
+themeload(char *s, int n)
+{
+ int i;
+ char *t, *a[2], *e;
+ Image *newc;
+ u32int rgb;
+
+ if((t = malloc(n+1)) == nil)
+ return;
+ memmove(t, s, n);
+ t[n] = 0;
+
+ for(s = t; s != nil && *s; s = e){
+ if((e = strchr(s, '\n')) != nil)
+ *e++ = 0;
+ if(tokenize(s, a, 2) == 2){
+ for(i = 0; i < nelem(theme); i++) {
+ if(strcmp(theme[i].id, a[0]) == 0) {
+ rgb = strtoul(a[1], nil, 16);
+ if((newc = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1, rgb<<8 | 0xff)) != nil) {
+ theme[i].rgb = rgb;
+ }
+ if(new != nil){
+ freeimage(col[i]);
+ col[i] = newc;
+ }
+ break;
+ }
+ }
+ }
+ }
+ free(t);
+}
+
+char *
+themestring(int *n)
+{
+ char *s, *t, *e;
+ int i;
+
+ if((t = malloc(512)) != nil){
+ s = t;
+ e = s+512;
+ for(i = 0; i < nelem(theme); i++)
+ s = seprint(s, e, "%s\t%06ux\n", theme[i].id, theme[i].rgb);
+ *n = s - t;
+ }
+
+ return t;
+}
--- /dev/null
+++ b/addr.c
@@ -1,0 +1,291 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+ None = 0,
+ Fore = '+',
+ Back = '-',
+};
+
+enum
+{
+ Char,
+ Line,
+};
+
+int
+isaddrc(int r)
+{
+ if(r && utfrune("0123456789+-/$.#,;", r)!=nil)
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * quite hard: could be almost anything but white space, but we are a little conservative,
+ * aiming for regular expressions of alphanumerics and no white space
+ */
+int
+isregexc(int r)
+{
+ if(r == 0)
+ return FALSE;
+ if(isalnum(r))
+ return TRUE;
+ if(utfrune("^+-.*?#,;[]()$", r)!=nil)
+ return TRUE;
+ return FALSE;
+}
+
+// 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->nc) {
+ if(textreadc(t, q0++) == '\n')
+ nl--;
+ }
+ if(nl > 0)
+ return q0;
+ while(nr > 0 && q0 < t->file->nc && textreadc(t, q0) != '\n') {
+ q0++;
+ nr--;
+ }
+ return q0;
+}
+
+Range
+number(Mntdir *md, Text *t, Range r, int line, int dir, int size, int *evalp)
+{
+ uint q0, q1;
+
+ if(size == Char){
+ if(dir == Fore)
+ line = r.q1+line;
+ else if(dir == Back){
+ if(r.q0==0 && line>0)
+ r.q0 = t->file->nc;
+ line = r.q0 - line;
+ }
+ if(line<0 || line>t->file->nc)
+ goto Rescue;
+ *evalp = TRUE;
+ return (Range){line, line};
+ }
+ q0 = r.q0;
+ q1 = r.q1;
+ switch(dir){
+ case None:
+ q0 = 0;
+ q1 = 0;
+ Forward:
+ while(line>0 && q1<t->file->nc)
+ if(textreadc(t, q1++) == '\n' || q1==t->file->nc)
+ if(--line > 0)
+ q0 = q1;
+ if(line > 0)
+ goto Rescue;
+ break;
+ case Fore:
+ if(q1 > 0)
+ while(q1<t->file->nc && textreadc(t, q1-1) != '\n')
+ q1++;
+ q0 = q1;
+ goto Forward;
+ case Back:
+ if(q0 < t->file->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(md != nil)
+ warning(nil, "address out of range\n");
+ *evalp = FALSE;
+ return r;
+}
+
+
+Range
+regexp(Mntdir *md, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp)
+{
+ int found;
+ Rangeset sel;
+ int q;
+
+ if(pat[0] == '\0' && rxnull()){
+ warning(md, "no previous regular expression\n");
+ *foundp = FALSE;
+ return r;
+ }
+ if(pat[0] && rxcompile(pat) == FALSE){
+ *foundp = FALSE;
+ return r;
+ }
+ if(dir == Back)
+ found = rxbexecute(t, r.q0, &sel);
+ else{
+ if(lim.q0 < 0)
+ q = Infinity;
+ else
+ q = lim.q1;
+ found = rxexecute(t, nil, r.q1, q, &sel);
+ }
+ if(!found && md==nil)
+ warning(nil, "no match for regexp\n");
+ *foundp = found;
+ return sel.r[0];
+}
+
+Range
+address(Mntdir *md, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp)
+{
+ int dir, size, npat;
+ int prevc, c, nc, n;
+ uint q;
+ Rune *pat;
+ Range r, nr;
+
+ r = ar;
+ q = q0;
+ dir = None;
+ size = Line;
+ c = 0;
+ while(q < q1){
+ prevc = c;
+ c = (*getc)(a, q++);
+ switch(c){
+ default:
+ *qp = q-1;
+ return r;
+ case ';':
+ ar = r;
+ /* fall through */
+ case ',':
+ if(prevc == 0) /* lhs defaults to 0 */
+ r.q0 = 0;
+ if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */
+ r.q1 = t->file->nc;
+ else{
+ nr = address(md, t, lim, ar, a, q, q1, getc, evalp, &q);
+ r.q1 = nr.q1;
+ }
+ *qp = q;
+ return r;
+ case '+':
+ case '-':
+ if(*evalp && (prevc=='+' || prevc=='-'))
+ if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?')
+ r = number(md, t, r, 1, prevc, Line, evalp); /* do previous one */
+ dir = c;
+ break;
+ case '.':
+ case '$':
+ if(q != q0+1){
+ *qp = q-1;
+ return r;
+ }
+ if(*evalp)
+ if(c == '.')
+ r = ar;
+ else
+ r = (Range){t->file->nc, t->file->nc};
+ if(q < q1)
+ dir = Fore;
+ else
+ dir = None;
+ break;
+ case '#':
+ if(q==q1 || (c=(*getc)(a, q++))<'0' || '9'<c){
+ *qp = q-1;
+ return r;
+ }
+ size = Char;
+ /* fall through */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = c -'0';
+ while(q<q1){
+ nc = (*getc)(a, q++);
+ if(nc<'0' || '9'<nc){
+ q--;
+ break;
+ }
+ n = n*10+(nc-'0');
+ }
+ if(*evalp)
+ r = number(md, t, r, n, dir, size, evalp);
+ dir = None;
+ size = Line;
+ break;
+ case '?':
+ dir = Back;
+ /* fall through */
+ case '/':
+ npat = 0;
+ pat = nil;
+ while(q<q1){
+ c = (*getc)(a, q++);
+ switch(c){
+ case '\n':
+ --q;
+ goto out;
+ case '\\':
+ pat = runerealloc(pat, npat+1);
+ pat[npat++] = c;
+ if(q == q1)
+ goto out;
+ c = (*getc)(a, q++);
+ break;
+ case '/':
+ goto out;
+ }
+ pat = runerealloc(pat, npat+1);
+ pat[npat++] = c;
+ }
+ out:
+ pat = runerealloc(pat, npat+1);
+ pat[npat] = 0;
+ if(*evalp)
+ r = regexp(md, t, lim, r, pat, dir, evalp);
+ free(pat);
+ dir = None;
+ size = Line;
+ break;
+ }
+ }
+ if(*evalp && dir != None)
+ r = number(md, t, r, 1, dir, Line, evalp); /* do previous one */
+ *qp = q;
+ return r;
+}
--- /dev/null
+++ b/buff.c
@@ -1,0 +1,322 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+ Slop = 100, /* room to grow with reallocation */
+};
+
+static
+void
+sizecache(Buffer *b, uint n)
+{
+ if(n <= b->cmax)
+ return;
+ b->cmax = n+Slop;
+ b->c = runerealloc(b->c, b->cmax);
+}
+
+static
+void
+addblock(Buffer *b, uint i, uint n)
+{
+ if(i > b->nbl)
+ error("internal error: addblock");
+
+ b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
+ if(i < b->nbl)
+ memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
+ b->bl[i] = disknewblock(disk, n);
+ b->nbl++;
+}
+
+static
+void
+delblock(Buffer *b, uint i)
+{
+ if(i >= b->nbl)
+ error("internal error: delblock");
+
+ diskrelease(disk, b->bl[i]);
+ b->nbl--;
+ if(i < b->nbl)
+ memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
+ b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
+}
+
+/*
+ * Move cache so b->cq <= q0 < b->cq+b->cnc.
+ * If at very end, q0 will fall on end of cache block.
+ */
+
+static
+void
+flush(Buffer *b)
+{
+ if(b->cdirty || b->cnc==0){
+ if(b->cnc == 0)
+ delblock(b, b->cbi);
+ else
+ diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
+ b->cdirty = FALSE;
+ }
+}
+
+static
+void
+setcache(Buffer *b, uint q0)
+{
+ Block **blp, *bl;
+ uint i, q;
+
+ if(q0 > b->nc)
+ error("internal error: setcache");
+ /*
+ * flush and reload if q0 is not in cache.
+ */
+ if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
+ return;
+ /*
+ * if q0 is at end of file and end of cache, continue to grow this block
+ */
+ if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<Maxblock)
+ return;
+ flush(b);
+ /* find block */
+ if(q0 < b->cq){
+ q = 0;
+ i = 0;
+ }else{
+ q = b->cq;
+ i = b->cbi;
+ }
+ blp = &b->bl[i];
+ while(q+(*blp)->n <= q0 && q+(*blp)->n < b->nc){
+ q += (*blp)->n;
+ i++;
+ blp++;
+ if(i >= b->nbl)
+ error("block not found");
+ }
+ bl = *blp;
+ /* remember position */
+ b->cbi = i;
+ b->cq = q;
+ sizecache(b, bl->n);
+ b->cnc = bl->n;
+ /*read block*/
+ diskread(disk, bl, b->c, b->cnc);
+}
+
+void
+bufinsert(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint i, m, t, off;
+
+ if(q0 > b->nc)
+ error("internal error: bufinsert");
+
+ while(n > 0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(b->cnc+n <= Maxblock){
+ /* Everything fits in one block. */
+ t = b->cnc+n;
+ m = n;
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ error("internal error: bufinsert1 cnc!=0");
+ addblock(b, 0, t);
+ b->cbi = 0;
+ }
+ sizecache(b, t);
+ runemove(b->c+off+m, b->c+off, b->cnc-off);
+ runemove(b->c+off, s, m);
+ b->cnc = t;
+ goto Tail;
+ }
+ /*
+ * We must make a new block. If q0 is at
+ * the very beginning or end of this block,
+ * just make a new block and fill it.
+ */
+ if(q0==b->cq || q0==b->cq+b->cnc){
+ if(b->cdirty)
+ flush(b);
+ m = min(n, Maxblock);
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ error("internal error: bufinsert2 cnc!=0");
+ i = 0;
+ }else{
+ i = b->cbi;
+ if(q0 > b->cq)
+ i++;
+ }
+ addblock(b, i, m);
+ sizecache(b, m);
+ runemove(b->c, s, m);
+ b->cq = q0;
+ b->cbi = i;
+ b->cnc = m;
+ goto Tail;
+ }
+ /*
+ * Split the block; cut off the right side and
+ * let go of it.
+ */
+ m = b->cnc-off;
+ if(m > 0){
+ i = b->cbi+1;
+ addblock(b, i, m);
+ diskwrite(disk, &b->bl[i], b->c+off, m);
+ b->cnc -= m;
+ }
+ /*
+ * Now at end of block. Take as much input
+ * as possible and tack it on end of block.
+ */
+ m = min(n, Maxblock-b->cnc);
+ sizecache(b, b->cnc+m);
+ runemove(b->c+b->cnc, s, m);
+ b->cnc += m;
+ Tail:
+ b->nc += m;
+ q0 += m;
+ s += m;
+ n -= m;
+ b->cdirty = TRUE;
+ }
+}
+
+void
+bufdelete(Buffer *b, uint q0, uint q1)
+{
+ uint m, n, off;
+
+ if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
+ error("internal error: bufdelete");
+ while(q1 > q0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(q1 > b->cq+b->cnc)
+ n = b->cnc - off;
+ else
+ n = q1-q0;
+ m = b->cnc - (off+n);
+ if(m > 0)
+ runemove(b->c+off, b->c+off+n, m);
+ b->cnc -= n;
+ b->cdirty = TRUE;
+ q1 -= n;
+ b->nc -= n;
+ }
+}
+
+static int
+bufloader(void *v, uint q0, Rune *r, int nr)
+{
+ bufinsert(v, q0, r, nr);
+ return nr;
+}
+
+uint
+loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg)
+{
+ char *p;
+ Rune *r;
+ int l, m, n, nb, nr;
+ uint q1;
+
+ p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]);
+ r = runemalloc(Maxblock);
+ m = 0;
+ n = 1;
+ q1 = q0;
+ /*
+ * At top of loop, may have m bytes left over from
+ * last pass, possibly representing a partial rune.
+ */
+ while(n > 0){
+ n = read(fd, p+m, Maxblock);
+ if(n < 0){
+ warning(nil, "read error in Buffer.load");
+ break;
+ }
+ m += n;
+ p[m] = 0;
+ l = m;
+ if(n > 0)
+ l -= UTFmax;
+ cvttorunes(p, l, r, &nb, &nr, nulls);
+ memmove(p, p+nb, m-nb);
+ m -= nb;
+ q1 += (*f)(arg, q1, r, nr);
+ }
+ free(p);
+ free(r);
+ return q1-q0;
+}
+
+uint
+bufload(Buffer *b, uint q0, int fd, int *nulls)
+{
+ if(q0 > b->nc)
+ error("internal error: bufload");
+ return loadfile(fd, q0, nulls, bufloader, b);
+}
+
+void
+bufread(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint m;
+
+ if(!(q0<=b->nc && q0+n<=b->nc))
+ error("bufread: internal error");
+
+ while(n > 0){
+ setcache(b, q0);
+ m = min(n, b->cnc-(q0-b->cq));
+ runemove(s, b->c+(q0-b->cq), m);
+ q0 += m;
+ s += m;
+ n -= m;
+ }
+}
+
+void
+bufreset(Buffer *b)
+{
+ int i;
+
+ b->nc = 0;
+ b->cnc = 0;
+ b->cq = 0;
+ b->cdirty = 0;
+ b->cbi = 0;
+ /* delete backwards to avoid n² behavior */
+ for(i=b->nbl-1; --i>=0; )
+ delblock(b, i);
+}
+
+void
+bufclose(Buffer *b)
+{
+ bufreset(b);
+ free(b->c);
+ b->c = nil;
+ b->cnc = 0;
+ free(b->bl);
+ b->bl = nil;
+ b->nbl = 0;
+}
--- /dev/null
+++ b/cols.c
@@ -1,0 +1,561 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+colinit(Column *c, Rectangle r)
+{
+ Rectangle r1;
+ Text *t;
+
+ draw(screen, r, display->white, nil, ZP);
+ c->r = r;
+ c->w = nil;
+ c->nw = 0;
+ t = &c->tag;
+ t->w = nil;
+ t->col = c;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
+ t->what = Columntag;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ textinsert(t, 0, L"New Cut Paste Snarf Sort Zerox Delcol ", 38, TRUE);
+ textsetselect(t, t->file->nc, t->file->nc);
+ draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
+ c->safe = TRUE;
+}
+
+Window*
+coladd(Column *c, Window *w, Window *clone, int y)
+{
+ Rectangle r, r1;
+ Window *v;
+ int i, t;
+
+ v = nil;
+ r = c->r;
+ r.min.y = c->tag.r.max.y+Border;
+ if(y<r.min.y && c->nw>0){ /* steal half of last window by default */
+ v = c->w[c->nw-1];
+ y = v->body.r.min.y+Dy(v->body.r)/2;
+ }
+ /* look for window we'll land on */
+ for(i=0; i<c->nw; i++){
+ v = c->w[i];
+ if(y < v->r.max.y)
+ break;
+ }
+ if(c->nw > 0){
+ if(i < c->nw)
+ i++; /* new window will go after v */
+ /*
+ * if v's too small, grow it first.
+ */
+ if(!c->safe || v->body.maxlines<=3){
+ colgrow(c, v, 1);
+ y = v->body.r.min.y+Dy(v->body.r)/2;
+ }
+ r = v->r;
+ if(i == c->nw)
+ t = c->r.max.y;
+ else
+ t = c->w[i]->r.min.y-Border;
+ r.max.y = t;
+ draw(screen, r, textcols[BACK], nil, ZP);
+ r1 = r;
+ y = min(y, t-(v->tag.font->height+v->body.font->height+Border+1));
+ r1.max.y = min(y, v->body.r.min.y+v->body.nlines*v->body.font->height);
+ r1.min.y = winresize(v, r1, FALSE);
+ r1.max.y = r1.min.y+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ }
+ if(w == nil){
+ w = emalloc(sizeof(Window));
+ w->rdselfd = -1;
+ w->col = c;
+ draw(screen, r, textcols[BACK], nil, ZP);
+ wininit(w, clone, r);
+ }else{
+ w->col = c;
+ winresize(w, r, FALSE);
+ }
+ w->tag.col = c;
+ w->tag.row = c->row;
+ w->body.col = c;
+ w->body.row = c->row;
+ c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
+ memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
+ c->nw++;
+ c->w[i] = w;
+ savemouse(w);
+ /* near but not on the button */
+ moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
+ barttext = &w->body;
+ c->safe = TRUE;
+ return w;
+}
+
+void
+colclose(Column *c, Window *w, int dofree)
+{
+ Rectangle r;
+ int i, didmouse, up;
+
+ /* w is locked */
+ if(!c->safe)
+ colgrow(c, w, 1);
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+ Found:
+ r = w->r;
+ w->tag.col = nil;
+ w->body.col = nil;
+ w->col = nil;
+ didmouse = restoremouse(w);
+ if(dofree){
+ windelete(w);
+ winclose(w);
+ }
+ c->nw--;
+ memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
+ c->w = realloc(c->w, c->nw*sizeof(Window*));
+ if(c->nw == 0){
+ draw(screen, r, display->white, nil, ZP);
+ return;
+ }
+ up = 0;
+ if(i == c->nw){ /* extend last window down */
+ w = c->w[i-1];
+ r.min.y = w->r.min.y;
+ r.max.y = c->r.max.y;
+ }else{ /* extend next window up */
+ up = 1;
+ w = c->w[i];
+ r.max.y = w->r.max.y;
+ }
+ draw(screen, r, textcols[BACK], nil, ZP);
+ if(c->safe) {
+ if(!didmouse && up)
+ w->showdel = TRUE;
+ winresize(w, r, FALSE);
+ if(!didmouse && up)
+ movetodel(w);
+ }
+}
+
+void
+colcloseall(Column *c)
+{
+ int i;
+ Window *w;
+
+ if(c == activecol)
+ activecol = nil;
+ textclose(&c->tag);
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ winclose(w);
+ }
+ c->nw = 0;
+ free(c->w);
+ free(c);
+ clearmouse();
+}
+
+void
+colmousebut(Column *c)
+{
+ moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
+}
+
+void
+colresize(Column *c, Rectangle r)
+{
+ int i, old, new;
+ Rectangle r1, r2;
+ Window *w;
+
+ clearmouse();
+ r1 = r;
+ r1.max.y = r1.min.y + c->tag.font->height;
+ textresize(&c->tag, r1);
+ draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r1.max.y = r.max.y;
+ new = Dy(r) - c->nw*(Border + font->height);
+ old = Dy(c->r) - c->nw*(Border + font->height);
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ w->maxlines = 0;
+ if(i == c->nw-1)
+ r1.max.y = r.max.y;
+ else {
+ r1.max.y = r1.min.y;
+ if(new > 0 && old > 0 && Dy(w->r) > font->height)
+ r1.max.y += (Dy(w->r)-font->height)*new/old + Border + font->height;
+ }
+ r2 = r1;
+ r2.max.y = r2.min.y+Border;
+ draw(screen, r2, display->black, nil, ZP);
+ r1.min.y = r2.max.y;
+ r1.min.y = winresize(w, r1, FALSE);
+ }
+ c->r = r;
+}
+
+static
+int
+colcmp(void *a, void *b)
+{
+ Rune *r1, *r2;
+ int i, nr1, nr2;
+
+ r1 = (*(Window**)a)->body.file->name;
+ nr1 = (*(Window**)a)->body.file->nname;
+ r2 = (*(Window**)b)->body.file->name;
+ nr2 = (*(Window**)b)->body.file->nname;
+ for(i=0; i<nr1 && i<nr2; i++){
+ if(*r1 != *r2)
+ return *r1-*r2;
+ r1++;
+ r2++;
+ }
+ return nr1-nr2;
+}
+
+void
+colsort(Column *c)
+{
+ int i, y;
+ Rectangle r, r1, *rp;
+ Window **wp, *w;
+
+ if(c->nw == 0)
+ return;
+ clearmouse();
+ rp = emalloc(c->nw*sizeof(Rectangle));
+ wp = emalloc(c->nw*sizeof(Window*));
+ memmove(wp, c->w, c->nw*sizeof(Window*));
+ qsort(wp, c->nw, sizeof(Window*), colcmp);
+ for(i=0; i<c->nw; i++)
+ rp[i] = wp[i]->r;
+ r = c->r;
+ r.min.y = c->tag.r.max.y;
+ draw(screen, r, textcols[BACK], nil, ZP);
+ y = r.min.y;
+ for(i=0; i<c->nw; i++){
+ w = wp[i];
+ r.min.y = y;
+ if(i == c->nw-1)
+ r.max.y = c->r.max.y;
+ else
+ r.max.y = r.min.y+Dy(w->r)+Border;
+ r1 = r;
+ r1.max.y = r1.min.y+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ y = winresize(w, r, FALSE);
+ }
+ free(rp);
+ free(c->w);
+ c->w = wp;
+}
+
+void
+colgrow(Column *c, Window *w, int but)
+{
+ Rectangle r, cr;
+ int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
+ Window *v;
+
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+
+ Found:
+ cr = c->r;
+ if(but < 0){ /* make sure window fills its own space properly */
+ r = w->r;
+ if(i==c->nw-1 || c->safe==FALSE)
+ r.max.y = cr.max.y;
+ else
+ r.max.y = c->w[i+1]->r.min.y;
+ winresize(w, r, FALSE);
+ return;
+ }
+ cr.min.y = c->w[0]->r.min.y;
+ if(but == 3){ /* full size */
+ if(i != 0){
+ v = c->w[0];
+ c->w[0] = w;
+ c->w[i] = v;
+ }
+ draw(screen, cr, textcols[BACK], nil, ZP);
+ winresize(w, cr, FALSE);
+ for(i=1; i<c->nw; i++)
+ c->w[i]->body.maxlines = 0;
+ c->safe = FALSE;
+ return;
+ }
+ /* store old #lines for each window */
+ onl = w->body.maxlines;
+ nl = emalloc(c->nw * sizeof(int));
+ ny = emalloc(c->nw * sizeof(int));
+ tot = 0;
+ for(j=0; j<c->nw; j++){
+ l = c->w[j]->body.maxlines;
+ nl[j] = l;
+ tot += l;
+ }
+ /* approximate new #lines for this window */
+ if(but == 2){ /* as big as can be */
+ memset(nl, 0, c->nw * sizeof(int));
+ goto Pack;
+ }
+ nnl = min(onl + max(min(5, w->maxlines), onl/2), tot);
+ if(nnl < w->maxlines)
+ nnl = (w->maxlines+nnl)/2;
+ if(nnl == 0)
+ nnl = 2;
+ dnl = nnl - onl;
+ /* compute new #lines for each window */
+ for(k=1; k<c->nw; k++){
+ /* prune from later window */
+ j = i+k;
+ if(j<c->nw && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ /* prune from earlier window */
+ j = i-k;
+ if(j>=0 && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ }
+ Pack:
+ /* pack everyone above */
+ y1 = cr.min.y;
+ for(j=0; j<i; j++){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y1;
+ r.max.y = y1+Dy(v->tag.all);
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v->body.font->height;
+ if(!c->safe || !eqrect(v->r, r)){
+ draw(screen, r, textcols[BACK], nil, ZP);
+ winresize(v, r, c->safe);
+ }
+ r.min.y = v->r.max.y;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ y1 = r.max.y;
+ }
+ /* scan to see new size of everyone below */
+ y2 = c->r.max.y;
+ for(j=c->nw-1; j>i; j--){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y2-Dy(v->tag.all);
+ if(nl[j])
+ r.min.y -= 1 + nl[j]*v->body.font->height;
+ r.min.y -= Border;
+ ny[j] = r.min.y;
+ y2 = r.min.y;
+ }
+ /* compute new size of window */
+ r = w->r;
+ r.min.y = y1;
+ r.max.y = r.min.y+Dy(w->tag.all);
+ h = w->body.font->height;
+ if(y2-r.max.y >= 1+h+Border){
+ r.max.y += 1;
+ r.max.y += h*((y2-r.max.y)/h);
+ }
+ /* draw window */
+ if(!c->safe || !eqrect(w->r, r)){
+ draw(screen, r, textcols[BACK], nil, ZP);
+ winresize(w, r, c->safe);
+ }
+ if(i < c->nw-1){
+ r.min.y = r.max.y;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ for(j=i+1; j<c->nw; j++)
+ ny[j] -= (y2-r.max.y);
+ }
+ /* pack everyone below */
+ y1 = r.max.y;
+ for(j=i+1; j<c->nw; j++){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y1;
+ r.max.y = y1+Dy(v->tag.all);
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v->body.font->height;
+ if(!c->safe || !eqrect(v->r, r)){
+ draw(screen, r, textcols[BACK], nil, ZP);
+ winresize(v, r, c->safe);
+ }
+ if(j < c->nw-1){ /* no border on last window */
+ r.min.y = v->r.max.y;
+ r.max.y = r.min.y + Border;
+ draw(screen, r, display->black, nil, ZP);
+ }
+ y1 = r.max.y;
+ }
+ r = w->r;
+ r.min.y = y1;
+ r.max.y = c->r.max.y;
+ draw(screen, r, textcols[BACK], nil, ZP);
+ free(nl);
+ free(ny);
+ c->safe = TRUE;
+ winmousebut(w);
+}
+
+void
+coldragwin(Column *c, Window *w, int but)
+{
+ Rectangle r;
+ int i, b;
+ Point p, op;
+ Window *v;
+ Column *nc;
+
+ clearmouse();
+ setcursor(mousectl, &boxcursor);
+ b = mouse->buttons;
+ op = mouse->xy;
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ setcursor(mousectl, nil);
+ if(mouse->buttons){
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return;
+ }
+
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+
+ Found:
+ p = mouse->xy;
+ if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
+ colgrow(c, w, but);
+ winmousebut(w);
+ return;
+ }
+ /* is it a flick to the right? */
+ if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
+ p.x = op.x+Dx(w->r); /* yes: toss to next column */
+ nc = rowwhichcol(c->row, p);
+ if(nc!=nil && nc!=c){
+ colclose(c, w, FALSE);
+ coladd(nc, w, nil, p.y);
+ winmousebut(w);
+ return;
+ }
+ if(i==0 && c->nw==1)
+ return; /* can't do it */
+ if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
+ || (i==0 && p.y>w->r.max.y)){
+ /* shuffle */
+ colclose(c, w, FALSE);
+ coladd(c, w, nil, p.y);
+ winmousebut(w);
+ return;
+ }
+ if(i == 0)
+ return;
+ v = c->w[i-1];
+ if(p.y < v->tag.all.max.y)
+ p.y = v->tag.all.max.y;
+ if(p.y > w->r.max.y-Dy(w->tag.all)-Border)
+ p.y = w->r.max.y-Dy(w->tag.all)-Border;
+ r = v->r;
+ r.max.y = p.y;
+ if(r.max.y > v->body.r.min.y){
+ r.max.y -= (r.max.y-v->body.r.min.y)%v->body.font->height;
+ if(v->body.r.min.y == v->body.r.max.y)
+ r.max.y++;
+ }
+ if(!eqrect(v->r, r)){
+ draw(screen, r, textcols[BACK], nil, ZP);
+ winresize(v, r, c->safe);
+ }
+ r.min.y = v->r.max.y;
+ r.max.y = r.min.y+Border;
+ draw(screen, r, display->black, nil, ZP);
+ r.min.y = r.max.y;
+ if(i == c->nw-1)
+ r.max.y = c->r.max.y;
+ else
+ r.max.y = c->w[i+1]->r.min.y-Border;
+ if(!eqrect(w->r, r)){
+ draw(screen, r, textcols[BACK], nil, ZP);
+ winresize(w, r, c->safe);
+ }
+ c->safe = TRUE;
+ winmousebut(w);
+}
+
+Text*
+colwhich(Column *c, Point p)
+{
+ int i;
+ Window *w;
+
+ if(!ptinrect(p, c->r))
+ return nil;
+ if(ptinrect(p, c->tag.all))
+ return &c->tag;
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ if(ptinrect(p, w->r)){
+ if(ptinrect(p, w->tag.all))
+ return &w->tag;
+ return &w->body;
+ }
+ /* scrollr drops below w->r on low windows */
+ if(ptinrect(p, w->body.scrollr))
+ return &w->body;
+ }
+ return nil;
+}
+
+int
+colclean(Column *c)
+{
+ int i, clean;
+
+ clean = TRUE;
+ for(i=0; i<c->nw; i++)
+ clean &= winclean(c->w[i], TRUE);
+ return clean;
+}
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,636 @@
+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 */
+};
+
+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 */
+ };
+};
+
+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*);
+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; /* the data */
+ Buffer delta; /* transcript of changes */
+ Buffer epsilon; /* inversion of delta for redo */
+ Buffer *elogbuf; /* log of pending editor changes */
+ Elog elog; /* current pending change */
+ Rune *name; /* name of associated file */
+ int nname; /* size of name */
+ uvlong qidpath; /* of file when read */
+ uint mtime; /* of file when read */
+ int dev; /* of file when read */
+ int unread; /* file has not been read from disk */
+ int editclean; /* mark clean after edit command */
+
+ int seq; /* if seq==0, File acts like Buffer */
+ int mod;
+ Text *curtext; /* most recently used associated text */
+ Text **text; /* list of associated texts */
+ int ntext;
+ int dumpid; /* used in dumping zeroxed windows */
+};
+File* fileaddtext(File*, Text*);
+void fileclose(File*);
+void filedelete(File*, uint, uint);
+void filedeltext(File*, Text*);
+void fileinsert(File*, uint, Rune*, uint);
+uint fileload(File*, uint, int, int*);
+void filemark(File*);
+void filereset(File*);
+void filesetname(File*, Rune*, int);
+void fileundelete(File*, Buffer*, uint, uint);
+void fileuninsert(File*, Buffer*, uint, uint);
+void fileunsetname(File*, Buffer*);
+void fileundo(File*, int, uint*, uint*);
+uint fileredoseq(File*);
+
+enum /* Text.what */
+{
+ Columntag,
+ Rowtag,
+ Tag,
+ Body,
+};
+
+struct Text
+{
+ File *file;
+ Frame;
+ Reffont *reffont;
+ uint org;
+ uint q0;
+ uint q1;
+ int what;
+ int tabstop;
+ Window *w;
+ Rectangle scrollr;
+ Rectangle lastsr;
+ Rectangle all;
+ Row *row;
+ Column *col;
+
+ uint eq0; /* start of typing for ESC */
+ uint cq0; /* cache position */
+ int ncache; /* storage for insert */
+ int ncachealloc;
+ Rune *cache;
+ int nofill;
+ int needundo;
+};
+
+uint textbacknl(Text*, uint, uint);
+uint textbsinsert(Text*, uint, Rune*, uint, int, int*);
+int textbswidth(Text*, Rune);
+int textclickmatch(Text*, int, int, int, uint*);
+void textclose(Text*);
+void textcolumnate(Text*, Dirlist**, int);
+void textcommit(Text*, int);
+void textconstrain(Text*, uint, uint, uint*, uint*);
+void textdelete(Text*, uint, uint, int);
+void textstretchsel(Text*, uint, uint*, uint*, int);
+void textfill(Text*);
+void textframescroll(Text*, int);
+void textinit(Text*, File*, Rectangle, Reffont*, Image**);
+void textinsert(Text*, uint, Rune*, uint, int);
+uint textload(Text*, uint, char*, int);
+Rune textreadc(Text*, uint);
+void textredraw(Text*, Rectangle, Font*, Image*, int);
+void textreset(Text*);
+int textresize(Text*, Rectangle);
+void textscrdraw(Text*);
+void textscroll(Text*, int);
+void textselect(Text*);
+int textselect2(Text*, uint*, uint*, Text**);
+int textselect23(Text*, uint*, uint*, Image*, int);
+int textselect3(Text*, uint*, uint*);
+void textsetorigin(Text*, uint, int);
+void textsetselect(Text*, uint, uint);
+void textshow(Text*, uint, uint, int);
+void texttype(Text*, Rune);
+
+enum
+{
+ SPACESINDENT = 0,
+ AUTOINDENT,
+ NINDENT,
+};
+
+struct Window
+{
+ QLock;
+ Ref;
+ Text tag;
+ Text body;
+ Rectangle r;
+ uchar isdir;
+ uchar isscratch;
+ uchar filemenu;
+ uchar dirty;
+ uchar indent[NINDENT];
+ uchar showdel;
+ uint noredraw;
+ int id;
+ Range addr;
+ Range limit;
+ uchar nopen[QMAX];
+ uchar nomark;
+ uchar noscroll;
+ 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;
+};
+
+void wininit(Window*, Window*, Rectangle);
+void winlock(Window*, int);
+void winlock1(Window*, int);
+void winunlock(Window*);
+void wintype(Window*, Text*, Rune);
+void winundo(Window*, int);
+void winsetname(Window*, Rune*, int);
+void winsettag(Window*);
+void winsettag1(Window*);
+void wincommit(Window*, Text*);
+int winresize(Window*, Rectangle, int);
+void winclose(Window*);
+void windelete(Window*);
+int winclean(Window*, int);
+void windirfree(Window*);
+void winevent(Window*, char*, ...);
+void winmousebut(Window*);
+void winaddincl(Window*, Rune*, int);
+void wincleartag(Window*);
+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;
+ 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;
+ 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;
+ 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;
+ };
+ int (*agetc)(void*, uint);
+ int a0;
+ int a1;
+};
+
+enum
+{
+ /* fbufalloc() guarantees room off end of BUFSIZE */
+ BUFSIZE = Maxblock+IOHDRSZ, /* size from fbufalloc() */
+ RBUFSIZE = BUFSIZE/sizeof(Rune),
+ EVENTSIZE = 256,
+ Scrollwid = 12, /* width of scroll bar */
+ Scrollgap = 4, /* gap right of scroll bar */
+ Margin = 4, /* margin around text */
+ Border = 2, /* line between rows, cols, windows */
+};
+
+#define QID(w,q) ((w<<8)|(q))
+#define WIN(q) ((((ulong)(q).path)>>8) & 0xFFFFFF)
+#define FILE(q) ((q).path & 0xFF)
+
+enum
+{
+ FALSE,
+ TRUE,
+ XXX,
+};
+
+enum
+{
+ Empty = 0,
+ Null = '-',
+ Delete = 'd',
+ Insert = 'i',
+ Replace = 'r',
+ Filename = 'f',
+};
+
+enum /* editing */
+{
+ Inactive = 0,
+ Inserting,
+ Collecting,
+};
+
+uint 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;
+Row row;
+int timerpid;
+Disk *disk;
+Text *seltext;
+Text *argtext;
+Text *mousetext; /* global because Text.close needs to clear it */
+Text *typetext; /* global because Text.close needs to clear it */
+Text *barttext; /* shared between mousetask and keyboardthread */
+int bartflag;
+Window *activewin;
+Column *activecol;
+Buffer snarfbuf;
+Rectangle nullrect;
+int fsyspid;
+char *user;
+char *cputype;
+char *objtype;
+char *home;
+char *fontnames[2];
+char acmeerrorfile[128];
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+int plumbsendfd;
+int plumbeditfd;
+char wdir[];
+int editing;
+int messagesize; /* negotiated in 9P version setup */
+int globalindent[NINDENT];
+Rune *delcmd; /* what command deleted the window. eg, Del, Delete, Delmesg */
+
+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]) */
+
+#define STACK 8192
+
+/* jgs1 - theme scructs */
+enum {
+ Colrioback,
+
+ /* the following group has to be in order, they are used by libframe */
+ Colback,
+ Colhigh,
+ Colbord,
+ Coltext,
+ Colhtext,
+
+ Coltitle,
+ Colltitle,
+ Colhold,
+ Collhold,
+ Colpalehold,
+ Colpaletext,
+ Colsize,
+
+ /* menuhit */
+ Colmenubar,
+ Colmenuback,
+ Colmenuhigh,
+ Colmenubord,
+ Colmenutext,
+ Colmenuhtext,
+
+ Numcolors
+};
+
+typedef struct Color Color;
+
+struct Color {
+ char *id;
+ union {
+ u32int rgb;
+ char *path;
+ };
+ int flags;
+};
+
+static Color theme[Numcolors] = {
+ [Colrioback] = {"rioback", {0x777777}, 0},
+ [Colback] = {"back", {0xffffff}, 0},
+ [Colhigh] = {"high", {0xcccccc}, 0},
+ [Colbord] = {"border", {0x999999}, 0},
+ [Coltext] = {"text", {DBlack>>8}, 0},
+ [Colhtext] = {"htext", {DBlack>>8}, 0},
+ [Coltitle] = {"title", {DGreygreen>>8}, 0},
+ [Colltitle] = {"ltitle", {DPalegreygreen>>8}, 0},
+ [Colhold] = {"hold", {DMedblue>>8}, 0},
+ [Collhold] = {"lhold", {DGreyblue>>8}, 0},
+ [Colpalehold] = {"palehold", {DPalegreyblue>>8}, 0},
+ [Colpaletext] = {"paletext", {0x666666}, 0},
+ [Colsize] = {"size", {DRed>>8}, 0},
+ [Colmenubar] = {"menubar", {DDarkgreen>>8}, 1},
+ [Colmenuback] = {"menuback", {0xeaffea}, 1},
+ [Colmenuhigh] = {"menuhigh", {DDarkgreen>>8}, 1},
+ [Colmenubord] = {"menubord", {DMedgreen>>8}, 1},
+ [Colmenutext] = {"menutext", {DBlack>>8}, 1},
+ [Colmenuhtext] = {"menuhtext", {0xeaffea}, 1},
+};
+
+Image *col[Numcolors];
\ No newline at end of file
--- /dev/null
+++ b/disk.c
@@ -1,0 +1,142 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Block *blist;
+
+int
+tempfile(void)
+{
+ char buf[128];
+ int i, fd;
+
+ snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), user);
+ 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->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].next = &blist[j+1];
+ }
+ b = blist;
+ blist = b->next;
+ b->addr = d->addr;
+ if(d->addr+size < d->addr){
+ error("temp file overflow");
+ }
+ d->addr += size;
+ }
+ b->n = n;
+ return b;
+}
+
+void
+diskrelease(Disk *d, Block *b)
+{
+ uint i;
+
+ ntosize(b->n, &i);
+ b->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->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->n = n;
+}
+
+void
+diskread(Disk *d, Block *b, Rune *r, uint n)
+{
+ int tot, nr;
+ char *p;
+
+ if(n > b->n)
+ error("internal error: diskread");
+
+ ntosize(b->n, nil);
+ n *= sizeof(Rune);
+ p = (char*)r;
+ for(tot = 0; tot < n; tot += nr){
+ nr = pread(d->fd, p+tot, n-tot, b->addr+tot);
+ if(nr <= 0)
+ error("read error from temp file");
+ }
+ if(tot != n)
+ error("read error from temp file");
+}
--- /dev/null
+++ b/ecmd.c
@@ -1,0 +1,1366 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "edit.h"
+#include "fns.h"
+
+int Glooping;
+int nest;
+char Enoname[] = "no file name given";
+
+Address addr;
+File *menu;
+Rangeset sel;
+extern Text* curtext;
+Rune *collection;
+int ncollection;
+
+int append(File*, Cmd*, long);
+int pdisplay(File*);
+void pfilename(File*);
+void looper(File*, Cmd*, int);
+void filelooper(Text*, Cmd*, int);
+void linelooper(File*, Cmd*);
+Address lineaddr(long, Address, int);
+int filematch(File*, String*);
+File *tofile(String*);
+Rune* cmdname(File *f, String *s, int);
+void runpipe(Text*, int, Rune*, int, int);
+
+void
+clearcollection(void)
+{
+ free(collection);
+ collection = nil;
+ ncollection = 0;
+}
+
+void
+resetxec(void)
+{
+ Glooping = nest = 0;
+ clearcollection();
+}
+
+void
+mkaddr(Address *a, File *f)
+{
+ a->r.q0 = f->curtext->q0;
+ a->r.q1 = f->curtext->q1;
+ a->f = f;
+}
+
+int
+cmdexec(Text *t, Cmd *cp)
+{
+ int i;
+ Addr *ap;
+ File *f;
+ Window *w;
+ Address dot;
+
+ if(t == nil)
+ w = nil;
+ else
+ w = t->w;
+ if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
+ !utfrune("bBnqUXY!", cp->cmdc) &&
+ !(cp->cmdc=='D' && cp->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->cmd; cp; cp = cp->next){
+ if(dot.r.q1 > t->file->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;
+
+ switch(editing){
+ case Inactive:
+ return "permission denied";
+ case Inserting:
+ f = w->body.file;
+ 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);
+ clearcollection();
+ if(r[0] != '<'){
+ if((collection = runestrdup(r)) != nil)
+ ncollection += runestrlen(r);
+ }else
+ /* use < command to collect text */
+ 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*, Cmd *cp)
+{
+ File *f;
+
+ f = tofile(cp->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->text->r, cp->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->text->r, cp->text->n);
+ t->q0 = addr.r.q0;
+ t->q1 = addr.r.q0;
+ return TRUE;
+}
+
+int
+d_cmd(Text *t, Cmd*)
+{
+ 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->text->r, cp->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->nc;
+ }
+ allreplaced = (q0==0 && q1==f->nc);
+ name = cmdname(f, cp->text, cp->cmdc=='e');
+ if(name == nil)
+ editerror(Enoname);
+ i = runestrlen(name);
+ samename = runeeq(name, i, t->file->name, t->file->nname);
+ s = runetobyte(name, i);
+ free(name);
+ fd = open(s, OREAD);
+ if(fd < 0){
+ snprint(tmp, sizeof tmp, "can't open %s: %r", s);
+ free(s);
+ editerror(tmp);
+ }
+ d = dirfstat(fd);
+ isdir = (d!=nil && (d->qid.type&QTDIR));
+ free(d);
+ if(isdir){
+ close(fd);
+ snprint(tmp, sizeof tmp, "%s is a directory", s);
+ free(s);
+ editerror(tmp);
+ }
+ elogdelete(f, q0, q1);
+ nulls = 0;
+ loadfile(fd, q1, &nulls, readloader, f);
+ free(s);
+ close(fd);
+ if(nulls)
+ warning(nil, "%s: NUL bytes elided\n", s);
+ else if(allreplaced && samename)
+ f->editclean = TRUE;
+ return TRUE;
+}
+
+int
+f_cmd(Text *t, Cmd *cp)
+{
+ Rune *name;
+ String *str;
+ String empty;
+
+ if(cp->text == nil){
+ empty.n = 0;
+ empty.r = L"";
+ str = ∅
+ }else
+ str = cp->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->cmd);
+ }
+ return TRUE;
+}
+
+int
+i_cmd(Text *t, Cmd *cp)
+{
+ return append(t->file, cp, addr.r.q0);
+}
+
+void
+copy(File *f, Address addr2)
+{
+ long p;
+ int ni;
+ Rune *buf;
+
+ buf = fbufalloc();
+ for(p=addr.r.q0; p<addr.r.q1; p+=ni){
+ ni = addr.r.q1-p;
+ if(ni > RBUFSIZE)
+ ni = RBUFSIZE;
+ bufread(f, 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->mtaddr, dot, 0);
+ if(cp->cmdc == 'm')
+ move(t->file, addr2);
+ else
+ copy(t->file, addr2);
+ return TRUE;
+}
+
+int
+p_cmd(Text *t, Cmd*)
+{
+ return pdisplay(t->file);
+}
+
+int
+s_cmd(Text *t, Cmd *cp)
+{
+ int i, j, k, c, m, n, nrp, didsub;
+ long p1, op, delta;
+ String *buf;
+ Rangeset *rp;
+ char *err;
+ Rune *rbuf;
+
+ n = cp->num;
+ op= -1;
+ if(rxcompile(cp->re->r) == FALSE)
+ editerror("bad regexp in s command");
+ nrp = 0;
+ rp = nil;
+ delta = 0;
+ didsub = FALSE;
+ for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
+ if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
+ if(sel.r[0].q0 == op){
+ p1++;
+ continue;
+ }
+ p1 = sel.r[0].q1+1;
+ }else
+ p1 = sel.r[0].q1;
+ op = sel.r[0].q1;
+ if(--n>0)
+ continue;
+ nrp++;
+ rp = erealloc(rp, nrp*sizeof(Rangeset));
+ rp[nrp-1] = sel;
+ }
+ rbuf = fbufalloc();
+ buf = allocstring(0);
+ for(m=0; m<nrp; m++){
+ buf->n = 0;
+ buf->r[0] = L'\0';
+ sel = rp[m];
+ for(i = 0; i<cp->text->n; i++)
+ if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){
+ c = cp->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, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
+ for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
+ Straddc(buf, rbuf[k]);
+ }else
+ Straddc(buf, c);
+ }else if(c!='&')
+ Straddc(buf, c);
+ else{
+ if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
+ err = "right hand side too long in substitution";
+ goto Err;
+ }
+ bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
+ for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
+ Straddc(buf, rbuf[k]);
+ }
+ elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
+ delta -= sel.r[0].q1-sel.r[0].q0;
+ delta += buf->n;
+ didsub = 1;
+ if(!cp->flag)
+ break;
+ }
+ free(rp);
+ freestring(buf);
+ fbuffree(rbuf);
+ if(!didsub && nest==0)
+ editerror("no substitution");
+ t->q0 = addr.r.q0;
+ t->q1 = addr.r.q1;
+ return TRUE;
+
+Err:
+ free(rp);
+ freestring(buf);
+ fbuffree(rbuf);
+ editerror(err);
+ return FALSE;
+}
+
+int
+u_cmd(Text *t, Cmd *cp)
+{
+ int n, oseq, flag;
+
+ n = cp->num;
+ flag = TRUE;
+ if(n < 0){
+ n = -n;
+ flag = FALSE;
+ }
+ oseq = -1;
+ while(n-->0 && t->file->seq!=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->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)
+{
+ filelooper(t, cp, cp->cmdc=='X');
+ return TRUE;
+}
+
+void
+runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
+{
+ Rune *r, *s;
+ int n;
+ Runestr dir;
+ Window *w;
+
+ 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); /* 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);
+ recvul(cedit);
+ qlock(&row);
+ editing = Inactive;
+ if(t!=nil && t->w!=nil)
+ winlock(t->w, 'M');
+}
+
+int
+pipe_cmd(Text *t, Cmd *cp)
+{
+ runpipe(t, cp->cmdc, cp->text->r, cp->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, 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+#%lud", l1, r1);
+ if(l2 != l1)
+ warning(nil, ",%lud+#%lud", l2, r2);
+ warning(nil, "\n");
+ return;
+ }
+}
+
+int
+eq_cmd(Text *t, Cmd *cp)
+{
+ int mode;
+
+ switch(cp->text->n){
+ case 0:
+ mode = PosnLine;
+ break;
+ case 1:
+ if(cp->text->r[0] == '#'){
+ mode = PosnChars;
+ break;
+ }
+ if(cp->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->text->n > 0)
+ eloginsert(f, p, cp->text->r, cp->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->nc)
+ p2 = f->nc;
+ buf = fbufalloc();
+ while(p1 < p2){
+ np = p2-p1;
+ if(np>RBUFSIZE-1)
+ np = RBUFSIZE-1;
+ bufread(f, p1, buf, np);
+ buf[np] = L'\0';
+ warning(nil, "%S", buf);
+ p1 += np;
+ }
+ fbuffree(buf);
+ f->curtext->q0 = addr.r.q0;
+ f->curtext->q1 = addr.r.q1;
+ return TRUE;
+}
+
+void
+pfilename(File *f)
+{
+ int dirty;
+ Window *w;
+
+ w = f->curtext->w;
+ /* same check for dirty as in settag, but we know ncache==0 */
+ dirty = !w->isdir && !w->isscratch && f->mod;
+ warning(nil, "%c%c%c %.*S\n", " '"[dirty],
+ '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
+}
+
+void
+loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
+{
+ long i;
+
+ for(i=0; i<nrp; i++){
+ f->curtext->q0 = rp[i].q0;
+ f->curtext->q1 = rp[i].q1;
+ cmdexec(f->curtext, cp);
+ }
+}
+
+void
+looper(File *f, Cmd *cp, int xy)
+{
+ long p, op, nrp;
+ Range r, tr;
+ Range *rp;
+
+ r = addr.r;
+ op= xy? -1 : r.q0;
+ nest++;
+ if(rxcompile(cp->re->r) == FALSE)
+ editerror("bad regexp in %c command", cp->cmdc);
+ nrp = 0;
+ rp = nil;
+ for(p = r.q0; p<=r.q1; ){
+ if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
+ if(xy || op>r.q1)
+ break;
+ tr.q0 = op, tr.q1 = r.q1;
+ p = r.q1+1; /* exit next loop */
+ }else{
+ if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
+ if(sel.r[0].q0==op){
+ p++;
+ continue;
+ }
+ p = sel.r[0].q1+1;
+ }else
+ p = sel.r[0].q1;
+ if(xy)
+ tr = sel.r[0];
+ else
+ tr.q0 = op, tr.q1 = sel.r[0].q0;
+ }
+ op = sel.r[0].q1;
+ nrp++;
+ rp = erealloc(rp, nrp*sizeof(Range));
+ rp[nrp-1] = tr;
+ }
+ loopcmd(f, cp->cmd, rp, nrp);
+ free(rp);
+ --nest;
+}
+
+void
+linelooper(File *f, Cmd *cp)
+{
+ long nrp, p;
+ Range r, linesel;
+ Address a, a3;
+ Range *rp;
+
+ nest++;
+ nrp = 0;
+ rp = nil;
+ r = addr.r;
+ a3.f = f;
+ a3.r.q0 = a3.r.q1 = r.q0;
+ a = lineaddr(0, a3, 1);
+ linesel = a.r;
+ for(p = r.q0; p<r.q1; p = a3.r.q1){
+ a3.r.q0 = a3.r.q1;
+ if(p!=r.q0 || linesel.q1==p){
+ a = lineaddr(1, a3, 1);
+ linesel = a.r;
+ }
+ if(linesel.q0 >= r.q1)
+ break;
+ if(linesel.q1 >= r.q1)
+ linesel.q1 = r.q1;
+ if(linesel.q1 > linesel.q0)
+ if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
+ a3.r = linesel;
+ nrp++;
+ rp = erealloc(rp, nrp*sizeof(Range));
+ rp[nrp-1] = linesel;
+ continue;
+ }
+ break;
+ }
+ loopcmd(f, cp->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);
+ else
+ winclose(w);
+}
+
+void
+filelooper(Text *t, Cmd *cp, int XY)
+{
+ int i;
+ Text *targ;
+
+ if(Glooping++)
+ editerror("can't nest %c command", "YX"[XY]);
+ nest++;
+
+ loopstruct.cp = cp;
+ loopstruct.XY = XY;
+ if(loopstruct.w) /* error'ed out last time */
+ free(loopstruct.w);
+ loopstruct.w = nil;
+ loopstruct.nw = 0;
+ allwindows(alllooper, &loopstruct);
+ /*
+ * add a ref to all windows to keep safe windows accessed by X
+ * that would not otherwise have a ref to hold them up during
+ * the shenanigans. note this with globalincref so that any
+ * newly created windows start with an extra reference.
+ */
+ allwindows(alllocker, (void*)1);
+ globalincref = 1;
+ /*
+ * Unlock the window running the X command.
+ * We'll need to lock and unlock each target window in turn.
+ */
+ if(t && t->w)
+ winunlock(t->w);
+ for(i=0; i<loopstruct.nw; i++){
+ targ = &loopstruct.w[i]->body;
+ if(targ && targ->w)
+ winlock(targ->w, cp->cmdc);
+ cmdexec(targ, cp->cmd);
+ if(targ && targ->w)
+ winunlock(targ->w);
+ }
+ if(t && t->w)
+ winlock(t->w, cp->cmdc);
+ allwindows(alllocker, (void*)0);
+ globalincref = 0;
+ free(loopstruct.w);
+ loopstruct.w = nil;
+
+ --Glooping;
+ --nest;
+}
+
+void
+nextmatch(File *f, String *r, long p, int sign)
+{
+ if(rxcompile(r->r) == FALSE)
+ editerror("bad regexp in command address");
+ if(sign >= 0){
+ if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
+ editerror("no match for regexp");
+ if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
+ if(++p>f->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->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->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->re, sign>=0? a.r.q1 : a.r.q0, sign);
+ a.r = sel.r[0];
+ break;
+
+ case '"':
+ f = matchfile(ap->re);
+ mkaddr(&a, f);
+ break;
+
+ case '*':
+ a.r.q0 = 0, a.r.q1 = f->nc;
+ return a;
+
+ case ',':
+ case ';':
+ if(ap->left)
+ a1 = cmdaddress(ap->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->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->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->nc)
+ editerror("address out of range");
+ if(textreadc(f->curtext, p++) == '\n')
+ n++;
+ }
+ a.r.q0 = p;
+ }
+ while(p < f->nc && textreadc(f->curtext, p++)!='\n')
+ ;
+ a.r.q1 = p;
+ }else{
+ p = addr.r.q0;
+ if(l == 0)
+ a.r.q1 = addr.r.q0;
+ else{
+ for(n = 0; n<l; ){ /* always runs once */
+ if(p == 0){
+ if(++n != l)
+ editerror("address out of range");
+ }else{
+ c = textreadc(f->curtext, p-1);
+ if(c != '\n' || ++n != l)
+ p--;
+ }
+ }
+ a.r.q1 = p;
+ if(p > 0)
+ p--;
+ }
+ while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
+ p--;
+ a.r.q0 = p;
+ }
+ return a;
+}
+
+struct Filecheck
+{
+ File *f;
+ Rune *r;
+ int nr;
+};
+
+void
+allfilecheck(Window *w, void *v)
+{
+ struct Filecheck *fp;
+ File *f;
+
+ fp = v;
+ f = w->body.file;
+ if(w->body.file == fp->f)
+ return;
+ if(runeeq(fp->r, fp->nr, f->name, f->nname))
+ warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
+}
+
+Rune*
+cmdname(File *f, String *str, int set)
+{
+ Rune *r, *s;
+ int n;
+ struct Filecheck fc;
+ Runestr newname;
+
+ r = nil;
+ n = str->n;
+ s = str->r;
+ if(n == 0){
+ /* no name; use existing */
+ if(f->nname == 0)
+ return nil;
+ r = runemalloc(f->nname+1);
+ runemove(r, f->name, f->nname);
+ return r;
+ }
+ s = skipbl(s, n, &n);
+ if(n == 0)
+ goto Return;
+
+ if(s[0] == '/'){
+ r = runemalloc(n+1);
+ runemove(r, s, n);
+ }else{
+ newname = dirname(f->curtext, runestrdup(s), n);
+ n = newname.nr;
+ r = runemalloc(n+1); /* NUL terminate */
+ runemove(r, newname.r, n);
+ free(newname.r);
+ }
+ fc.f = f;
+ fc.r = r;
+ fc.nr = n;
+ allwindows(allfilecheck, &fc);
+ if(f->nname == 0)
+ set = TRUE;
+
+ Return:
+ if(set && !runeeq(r, n, f->name, f->nname)){
+ filemark(f);
+ f->mod = TRUE;
+ f->curtext->w->dirty = TRUE;
+ winsetname(f->curtext->w, r, n);
+ }
+ return r;
+}
--- /dev/null
+++ b/edit.c
@@ -1,0 +1,679 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "edit.h"
+#include "fns.h"
+
+static char linex[]="\n";
+static char wordx[]=" \t\n";
+struct cmdtab cmdtab[]={
+/* cmdc text regexp addr defcmd defaddr count token fn */
+ '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd,
+ 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd,
+ 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
+ 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd,
+ 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd,
+ 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd,
+ 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd,
+ 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd,
+ 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd,
+ 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd,
+ 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd,
+ 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd,
+ 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd,
+ 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd,
+ 'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd,
+ 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd,
+ 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ '<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
+ '|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
+ '>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
+/* deliberately unimplemented:
+ 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd,
+ 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd,
+ 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd,
+ '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd,
+ */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+Cmd *parsecmd(int);
+Addr *compoundaddr(void);
+Addr *simpleaddr(void);
+void freecmd(void);
+void okdelim(int);
+
+Rune *cmdstartp;
+Rune *cmdendp;
+Rune *cmdp;
+Channel *editerrc;
+
+String *lastpat;
+int patset;
+
+List cmdlist;
+List addrlist;
+List stringlist;
+Text *curtext;
+int editing = Inactive;
+
+String* newstring(int);
+
+void
+editthread(void*)
+{
+ Cmd *cmdp;
+
+ threadsetname("editthread");
+ while((cmdp=parsecmd(0)) != 0){
+// ocurfile = curfile;
+// loaded = curfile && !curfile->unread;
+ if(cmdexec(curtext, cmdp) == 0)
+ break;
+ freecmd();
+ }
+ sendp(editerrc, nil);
+}
+
+void
+allelogterm(Window *w, void*)
+{
+ elogterm(w->body.file);
+}
+
+void
+alleditinit(Window *w, void*)
+{
+ textcommit(&w->tag, TRUE);
+ textcommit(&w->body, TRUE);
+ w->body.file->editclean = FALSE;
+}
+
+void
+allupdate(Window *w, void*)
+{
+ Text *t;
+ int i;
+ File *f;
+
+ t = &w->body;
+ f = t->file;
+ if(f->curtext != t) /* do curtext only */
+ return;
+ if(f->elog.type == Null)
+ elogterm(f);
+ else if(f->elog.type != Empty){
+ elogapply(f);
+ if(f->editclean){
+ f->mod = FALSE;
+ for(i=0; i<f->ntext; i++)
+ f->text[i]->w->dirty = FALSE;
+ }
+ }
+ textsetselect(t, t->q0, t->q1);
+ textscrdraw(t);
+ winsettag(w);
+}
+
+void
+editerror(char *fmt, ...)
+{
+ va_list arg;
+ char *s;
+
+ va_start(arg, fmt);
+ s = vsmprint(fmt, arg);
+ va_end(arg);
+ freecmd();
+ allwindows(allelogterm, nil); /* truncate the edit logs */
+ sendp(editerrc, s);
+ threadexits(nil);
+}
+
+void
+editcmd(Text *ct, Rune *r, uint n)
+{
+ char *err;
+
+ if(n == 0)
+ return;
+ if(2*n > RBUFSIZE){
+ warning(nil, "string too long\n");
+ return;
+ }
+
+ allwindows(alleditinit, nil);
+ if(cmdstartp)
+ free(cmdstartp);
+ cmdstartp = runemalloc(n+2);
+ runemove(cmdstartp, r, n);
+ if(r[n] != '\n')
+ cmdstartp[n++] = '\n';
+ cmdstartp[n] = '\0';
+ cmdendp = cmdstartp+n;
+ cmdp = cmdstartp;
+ if(ct->w == nil)
+ curtext = nil;
+ else
+ curtext = &ct->w->body;
+ resetxec();
+ if(editerrc == nil){
+ editerrc = chancreate(sizeof(char*), 0);
+ lastpat = allocstring(0);
+ }
+ threadcreate(editthread, nil, STACK);
+ err = recvp(editerrc);
+ editing = Inactive;
+ if(err != nil){
+ if(err[0] != '\0')
+ warning(nil, "Edit: %s\n", err);
+ free(err);
+ }
+
+ /* update everyone whose edit log has data */
+ allwindows(allupdate, nil);
+}
+
+int
+getch(void)
+{
+ if(*cmdp == *cmdendp)
+ return -1;
+ return *cmdp++;
+}
+
+int
+nextc(void)
+{
+ if(*cmdp == *cmdendp)
+ return -1;
+ return *cmdp;
+}
+
+void
+ungetch(void)
+{
+ if(--cmdp < cmdstartp)
+ error("ungetch");
+}
+
+long
+getnum(int signok)
+{
+ long n;
+ int c, sign;
+
+ n = 0;
+ sign = 1;
+ if(signok>1 && nextc()=='-'){
+ sign = -1;
+ getch();
+ }
+ if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
+ return sign;
+ while('0'<=(c=getch()) && c<='9')
+ n = n*10 + (c-'0');
+ ungetch();
+ return sign*n;
+}
+
+int
+cmdskipbl(void)
+{
+ int c;
+ do
+ c = getch();
+ while(c==' ' || c=='\t');
+ if(c >= 0)
+ ungetch();
+ return c;
+}
+
+/*
+ * Check that list has room for one more element.
+ */
+void
+growlist(List *l)
+{
+ if(l->listptr==0 || l->nalloc==0){
+ l->nalloc = INCR;
+ l->listptr = emalloc(INCR*sizeof(void*));
+ l->nused = 0;
+ }else if(l->nused == l->nalloc){
+ l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(void*));
+ memset(l->ptr+l->nalloc, 0, INCR*sizeof(void*));
+ l->nalloc += INCR;
+ }
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+ l->nused--;
+ memmove(&l->ptr[i], &l->ptr[i+1], (l->nused-i)*sizeof(void*));
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, void *v)
+{
+ growlist(l);
+ memmove(&l->ptr[i+1], &l->ptr[i], (l->nused-i)*sizeof(void*));
+ l->ptr[i] = v;
+ l->nused++;
+}
+
+void
+listfree(List *l)
+{
+ free(l->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.ucharptr[--cmdlist.nused]);
+ while(addrlist.nused > 0)
+ free(addrlist.ucharptr[--addrlist.nused]);
+ while(stringlist.nused>0){
+ i = --stringlist.nused;
+ freestring(stringlist.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.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.text = newstring(0);
+ getrhs(cmd.text, c, 's');
+ if(nextc() == c){
+ getch();
+ if(nextc() == 'g')
+ cmd.flag = getch();
+ }
+
+ }
+ }
+ }
+ if(ct->addr && (cmd.mtaddr=simpleaddr())==0)
+ editerror("bad address");
+ if(ct->defcmd){
+ if(cmdskipbl() == '\n'){
+ getch();
+ cmd.cmd = newcmd();
+ cmd.cmd->cmdc = ct->defcmd;
+ }else if((cmd.cmd = parsecmd(nest))==0)
+ error("defcmd");
+ }else if(ct->text)
+ cmd.text = collecttext();
+ else if(ct->token)
+ cmd.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.cmd = ncp;
+ }while(cp = ncp);
+ break;
+ case '}':
+ atnl();
+ if(nest==0)
+ editerror("right brace with no left brace");
+ return 0;
+ default:
+ editerror("unknown command %c", cmd.cmdc);
+ }
+ Return:
+ cp = newcmd();
+ *cp = cmd;
+ return cp;
+}
+
+String*
+getregexp(int delim)
+{
+ String *buf, *r;
+ int i, c;
+
+ buf = allocstring(0);
+ for(i=0; ; i++){
+ if((c = getch())=='\\'){
+ if(nextc()==delim)
+ c = getch();
+ else if(nextc()=='\\'){
+ Straddc(buf, c);
+ c = getch();
+ }
+ }else if(c==delim || c=='\n')
+ break;
+ if(i >= RBUFSIZE)
+ editerror("regular expression too long");
+ Straddc(buf, c);
+ }
+ if(c!=delim && c)
+ ungetch();
+ if(buf->n > 0){
+ patset = TRUE;
+ freestring(lastpat);
+ lastpat = buf;
+ }else
+ freestring(buf);
+ if(lastpat->n == 0)
+ editerror("no regular expression defined");
+ r = newstring(lastpat->n);
+ runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */
+ return r;
+}
+
+Addr *
+simpleaddr(void)
+{
+ Addr addr;
+ Addr *ap, *nap;
+
+ addr.next = 0;
+ addr.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.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.left = simpleaddr();
+ if((addr.type = cmdskipbl())!=',' && addr.type!=';')
+ return addr.left;
+ getch();
+ next = addr.next = compoundaddr();
+ if(next && (next->type==',' || next->type==';') && next->left==0)
+ editerror("bad address syntax");
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
--- /dev/null
+++ b/edit.h
@@ -1,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 ; */
+ };
+ 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 */
+ };
+ 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
+{
+ int nalloc;
+ int nused;
+ union{
+ void *listptr;
+ void* *ptr;
+ uchar* *ucharptr;
+ String* *stringptr;
+ };
+};
+
+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);
--- /dev/null
+++ b/elog.c
@@ -1,0 +1,353 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+#include "edit.h"
+
+static char Wsequence[] = "warning: changes out of sequence\n";
+static int warned = FALSE;
+
+/*
+ * Log of changes made by editing commands. Three reasons for this:
+ * 1) We want addresses in commands to apply to old file, not file-in-change.
+ * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
+ * 3) This gives an opportunity to optimize by merging adjacent changes.
+ * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
+ * separate implementation. To do this well, we use Replace as well as
+ * Insert and Delete
+ */
+
+typedef struct Buflog Buflog;
+struct Buflog
+{
+ short type; /* Replace, Filename */
+ uint q0; /* location of change (unused in f) */
+ uint nd; /* # runes to delete */
+ uint nr; /* # runes in string or file name */
+};
+
+enum
+{
+ Buflogsize = sizeof(Buflog)/sizeof(Rune),
+};
+
+/*
+ * Minstring shouldn't be very big or we will do lots of I/O for small changes.
+ * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
+ */
+enum
+{
+ Minstring = 16, /* distance beneath which we merge changes */
+ Maxstring = RBUFSIZE, /* maximum length of change we will merge into one */
+};
+
+void
+eloginit(File *f)
+{
+ if(f->elog.type != Empty)
+ return;
+ f->elog.type = Null;
+ if(f->elogbuf == nil)
+ f->elogbuf = emalloc(sizeof(Buffer));
+ if(f->elog.r == nil)
+ f->elog.r = fbufalloc();
+ bufreset(f->elogbuf);
+}
+
+void
+elogclose(File *f)
+{
+ if(f->elogbuf){
+ bufclose(f->elogbuf);
+ free(f->elogbuf);
+ f->elogbuf = nil;
+ }
+}
+
+void
+elogreset(File *f)
+{
+ f->elog.type = Null;
+ f->elog.nd = 0;
+ f->elog.nr = 0;
+}
+
+void
+elogterm(File *f)
+{
+ elogreset(f);
+ if(f->elogbuf)
+ bufreset(f->elogbuf);
+ f->elog.type = Empty;
+ fbuffree(f->elog.r);
+ f->elog.r = nil;
+ warned = FALSE;
+}
+
+void
+elogflush(File *f)
+{
+ Buflog b;
+
+ b.type = f->elog.type;
+ b.q0 = f->elog.q0;
+ b.nd = f->elog.nd;
+ b.nr = f->elog.nr;
+ switch(f->elog.type){
+ default:
+ warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
+ break;
+ case Null:
+ break;
+ case Insert:
+ case Replace:
+ if(f->elog.nr > 0)
+ bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
+ /* fall through */
+ case Delete:
+ bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
+ break;
+ }
+ elogreset(f);
+}
+
+void
+elogreplace(File *f, int q0, int q1, Rune *r, int nr)
+{
+ uint gap;
+
+ if(q0==q1 && nr==0)
+ return;
+ eloginit(f);
+ if(f->elog.type!=Null && q0<f->elog.q0){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ /* try to merge with previous */
+ gap = q0 - (f->elog.q0+f->elog.nd); /* gap between previous and this */
+ if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
+ if(gap < Minstring){
+ if(gap > 0){
+ bufread(f, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
+ f->elog.nr += gap;
+ }
+ f->elog.nd += gap + q1-q0;
+ runemove(f->elog.r+f->elog.nr, r, nr);
+ f->elog.nr += nr;
+ return;
+ }
+ }
+ elogflush(f);
+ f->elog.type = Replace;
+ f->elog.q0 = q0;
+ f->elog.nd = q1-q0;
+ f->elog.nr = nr;
+ if(nr > RBUFSIZE)
+ editerror("internal error: replacement string too large(%d)", nr);
+ runemove(f->elog.r, r, nr);
+}
+
+void
+eloginsert(File *f, int q0, Rune *r, int nr)
+{
+ int n;
+
+ if(nr == 0)
+ return;
+ eloginit(f);
+ if(f->elog.type!=Null && q0<f->elog.q0){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ /* try to merge with previous */
+ if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
+ runemove(f->elog.r+f->elog.nr, r, nr);
+ f->elog.nr += nr;
+ return;
+ }
+ while(nr > 0){
+ elogflush(f);
+ f->elog.type = Insert;
+ f->elog.q0 = q0;
+ n = nr;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ f->elog.nr = n;
+ runemove(f->elog.r, r, n);
+ r += n;
+ nr -= n;
+ }
+}
+
+void
+elogdelete(File *f, int q0, int q1)
+{
+ if(q0 == q1)
+ return;
+ eloginit(f);
+ if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ /* try to merge with previous */
+ if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
+ f->elog.nd += q1-q0;
+ return;
+ }
+ elogflush(f);
+ f->elog.type = Delete;
+ f->elog.q0 = q0;
+ f->elog.nd = q1-q0;
+}
+
+#define tracelog 0
+void
+elogapply(File *f)
+{
+ Buflog b;
+ Rune *buf;
+ uint i, n, up, mod;
+ uint tq0, tq1;
+ Buffer *log;
+ Text *t;
+ int owner;
+
+ elogflush(f);
+ log = f->elogbuf;
+ t = f->curtext;
+
+ buf = fbufalloc();
+ mod = FALSE;
+
+ owner = 0;
+ if(t->w){
+ owner = t->w->owner;
+ if(owner == 0)
+ t->w->owner = 'E';
+ }
+
+ /*
+ * The edit commands have already updated the selection in t->q0, t->q1,
+ * but using coordinates relative to the unmodified buffer. As we apply the log,
+ * we have to update the coordinates to be relative to the modified buffer.
+ * Textinsert and textdelete will do this for us; our only work is to apply the
+ * convention that an insertion at t->q0==t->q1 is intended to select the
+ * inserted text.
+ */
+
+ /*
+ * We constrain the addresses in here (with textconstrain()) because
+ * overlapping changes will generate bogus addresses. We will warn
+ * about changes out of sequence but proceed anyway; here we must
+ * keep things in range.
+ */
+
+ while(log->nc > 0){
+ up = log->nc-Buflogsize;
+ bufread(log, up, (Rune*)&b, Buflogsize);
+ switch(b.type){
+ default:
+ fprint(2, "elogapply: 0x%ux\n", b.type);
+ abort();
+ break;
+
+ case Replace:
+ if(tracelog)
+ warning(nil, "elog replace %d %d (%d %d)\n",
+ b.q0, b.q0+b.nd, t->q0, t->q1);
+ if(!mod){
+ mod = TRUE;
+ filemark(f);
+ }
+ textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
+ textdelete(t, tq0, tq1, TRUE);
+ up -= b.nr;
+ for(i=0; i<b.nr; i+=n){
+ n = b.nr - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(log, up+i, buf, n);
+ textinsert(t, tq0+i, buf, n, TRUE);
+ }
+ if(t->q0 == b.q0 && t->q1 == b.q0)
+ t->q1 += b.nr;
+ break;
+
+ case Delete:
+ if(tracelog)
+ warning(nil, "elog delete %d %d (%d %d)\n",
+ b.q0, b.q0+b.nd, t->q0, t->q1);
+ if(!mod){
+ mod = TRUE;
+ filemark(f);
+ }
+ textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
+ textdelete(t, tq0, tq1, TRUE);
+ break;
+
+ case Insert:
+ if(tracelog)
+ warning(nil, "elog insert %d %d (%d %d)\n",
+ b.q0, b.q0+b.nr, t->q0, t->q1);
+ if(!mod){
+ mod = TRUE;
+ filemark(f);
+ }
+ textconstrain(t, b.q0, b.q0, &tq0, &tq1);
+ up -= b.nr;
+ for(i=0; i<b.nr; i+=n){
+ n = b.nr - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(log, up+i, buf, n);
+ textinsert(t, tq0+i, buf, n, TRUE);
+ }
+ if(t->q0 == b.q0 && t->q1 == b.q0)
+ t->q1 += b.nr;
+ break;
+
+/* case Filename:
+ f->seq = u.seq;
+ fileunsetname(f, epsilon);
+ f->mod = u.mod;
+ up -= u.n;
+ free(f->name);
+ if(u.n == 0)
+ f->name = nil;
+ else
+ f->name = runemalloc(u.n);
+ bufread(delta, up, f->name, u.n);
+ f->nname = u.n;
+ break;
+*/
+ }
+ bufdelete(log, up, log->nc);
+ }
+ fbuffree(buf);
+ elogterm(f);
+
+ /*
+ * Bad addresses will cause bufload to crash, so double check.
+ * If changes were out of order, we expect problems so don't complain further.
+ */
+ if(t->q0 > f->nc || t->q1 > f->nc || t->q0 > t->q1){
+ if(!warned)
+ warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->nc);
+ t->q1 = min(t->q1, f->nc);
+ t->q0 = min(t->q0, t->q1);
+ }
+
+ if(t->w)
+ t->w->owner = owner;
+}
--- /dev/null
+++ b/exec.c
@@ -1,0 +1,1501 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+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 del(Text*, Text*, Text*, int, int, Rune*, int);
+void delcol(Text*, Text*, Text*, int, int, Rune*, int);
+void dump(Text*, Text*, Text*, int, int, Rune*, int);
+void edit(Text*, Text*, Text*, int, int, Rune*, int);
+void exit(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 kill(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;
+};
+
+Exectab exectab[] = {
+ { L"Cut", cut, TRUE, TRUE, TRUE },
+ { L"Del", del, FALSE, FALSE, XXX },
+ { L"Delcol", delcol, FALSE, XXX, XXX },
+ { L"Delete", del, FALSE, TRUE, XXX },
+ { L"Dump", dump, FALSE, TRUE, XXX },
+ { L"Edit", edit, FALSE, XXX, XXX },
+ { L"Exit", exit, FALSE, XXX, XXX },
+ { L"Font", fontx, FALSE, XXX, XXX },
+ { L"Get", get, FALSE, TRUE, XXX },
+ { L"ID", id, FALSE, XXX, XXX },
+ { L"Incl", incl, FALSE, XXX, XXX },
+ { L"Indent", indent, FALSE, AUTOINDENT, XXX },
+ { L"Kill", kill, FALSE, XXX, XXX },
+ { L"Load", dump, FALSE, FALSE, XXX },
+ { L"Local", local, FALSE, XXX, XXX },
+ { L"Look", look, FALSE, XXX, XXX },
+ { L"New", new, FALSE, XXX, XXX },
+ { L"Newcol", newcol, FALSE, XXX, XXX },
+ { L"Paste", paste, TRUE, TRUE, XXX },
+ { L"Put", put, FALSE, XXX, XXX },
+ { L"Putall", putall, FALSE, XXX, XXX },
+ { L"Redo", undo, FALSE, FALSE, XXX },
+ { L"Send", sendx, TRUE, XXX, XXX },
+ { L"Snarf", cut, FALSE, TRUE, FALSE },
+ { L"Sort", sort, FALSE, XXX, XXX },
+ { L"Spaces", indent, FALSE, SPACESINDENT, XXX },
+ { L"Tab", tab, FALSE, XXX, XXX },
+ { L"Undo", undo, FALSE, TRUE, XXX },
+ { L"Zerox", zeroxx, FALSE, XXX, XXX },
+ { nil, nil, 0, 0, 0 },
+};
+
+Exectab*
+lookup(Rune *r, int n)
+{
+ Exectab *e;
+ int nr;
+
+ r = skipbl(r, n, &n);
+ if(n == 0)
+ return nil;
+ findbl(r, n, &nr);
+ nr = n-nr;
+ for(e=exectab; e->name; e++)
+ if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
+ return e;
+ return nil;
+}
+
+int
+isexecc(int c)
+{
+ if(isfilec(c))
+ return 1;
+ return c=='<' || c=='|' || c=='>';
+}
+
+void
+execute(Text *t, uint aq0, uint aq1, int external, Text *argt)
+{
+ uint q0, q1;
+ Rune *r, *s;
+ char *b, *a, *aa;
+ Exectab *e;
+ int c, n, f;
+ Runestr dir;
+
+ q0 = aq0;
+ q1 = aq1;
+ if(q1 == q0){ /* expand to find word (actually file name) */
+ /* if in selection, choose selection */
+ if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ }else{
+ while(q1<t->file->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, q0, r, q1-q0);
+ free(delcmd);
+ delcmd = runesmprint("%.*S", q1-q0, r);
+ 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, 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, 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);
+ 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, e.q0, *rp, n);
+ if(doaddr)
+ a = printarg(argt, e.q0, e.q1);
+ *nrp = n;
+ return a;
+}
+
+char*
+getbytearg(Text *argt, int doaddr, int dofile, char **bp)
+{
+ Rune *r;
+ int n;
+ char *aa;
+
+ *bp = nil;
+ aa = getarg(argt, doaddr, dofile, &r, &n);
+ if(r == nil)
+ return nil;
+ *bp = runetobyte(r, n);
+ free(r);
+ return aa;
+}
+
+void
+newcol(Text *et, Text*, Text*, int, int, Rune*, int)
+{
+ Column *c;
+ Window *w;
+
+ c = rowadd(et->row, nil, -1);
+ if(c) {
+ w = coladd(c, nil, nil, -1);
+ winsettag(w);
+ xfidlog(w, "new");
+ }
+}
+
+void
+delcol(Text *et, Text*, Text*, int, int, Rune*, int)
+{
+ int i;
+ Column *c;
+ Window *w;
+
+ c = et->col;
+ if(c==nil || colclean(c)==0)
+ return;
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata]+w->nopen[QWxdata] > 0){
+ warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name);
+ return;
+ }
+ }
+ rowclose(et->col->row, et->col, TRUE);
+}
+
+void
+del(Text *et, Text*, Text *argt, int flag1, int, Rune *arg, int narg)
+{
+ Window *w;
+ char *name, *p;
+ Plumbmsg *pm;
+
+ if(et->col==nil || et->w == nil)
+ return;
+ if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE)){
+ w = et->w;
+ name = getname(&w->body, argt, arg, narg, TRUE);
+ if(name && plumbsendfd >= 0){
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("acme");
+ pm->dst = estrdup("close");
+ pm->wdir = estrdup(name);
+ if(p = strrchr(pm->wdir, '/'))
+ *p = '\0';
+ pm->type = estrdup("text");
+ pm->attr = nil;
+ pm->data = estrdup(name);
+ pm->ndata = strlen(pm->data);
+ if(pm->ndata < messagesize-1024)
+ plumbsend(plumbsendfd, pm);
+ else
+ plumbfree(pm);
+ }
+ colclose(et->col, et->w, TRUE);
+ }
+}
+
+void
+sort(Text *et, Text*, Text*, int, int, Rune*, int)
+{
+ 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*, Text*, int flag1, int, Rune*, int)
+{
+ int i, j;
+ Column *c;
+ Window *w;
+ uint seq;
+
+ if(et==nil || et->w== nil)
+ return;
+ seq = seqof(et->w, flag1);
+ if(seq == 0){
+ /* nothing to undo */
+ return;
+ }
+ /*
+ * Undo the executing window first. Its display will update. other windows
+ * in the same file will not call show() and jump to a different location in the file.
+ * Simultaneous changes to other files will be chaotic, however.
+ */
+ winundo(et->w, flag1);
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c->nw; j++){
+ w = c->w[j];
+ if(w == et->w)
+ continue;
+ if(seqof(w, flag1) == seq)
+ winundo(w, flag1);
+ }
+ }
+}
+
+char*
+getname(Text *t, Text *argt, Rune *arg, int narg, int isput)
+{
+ char *s;
+ Rune *r;
+ int i, n, promote;
+ Runestr dir;
+
+ getarg(argt, FALSE, TRUE, &r, &n);
+ promote = FALSE;
+ if(r == nil)
+ promote = TRUE;
+ else if(isput){
+ /* if are doing a Put, want to synthesize name even for non-existent file */
+ /* best guess is that file name doesn't contain a slash */
+ promote = TRUE;
+ for(i=0; i<n; i++)
+ if(r[i] == '/'){
+ promote = FALSE;
+ break;
+ }
+ if(promote){
+ t = argt;
+ arg = r;
+ narg = n;
+ }
+ }
+ if(promote){
+ n = narg;
+ if(n <= 0){
+ s = runetobyte(t->file->name, t->file->nname);
+ return s;
+ }
+ /* prefix with directory name if necessary */
+ dir.r = nil;
+ dir.nr = 0;
+ if(n>0 && arg[0]!='/'){
+ dir = dirname(t, nil, 0);
+ if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
+ free(dir.r);
+ dir.r = nil;
+ dir.nr = 0;
+ }
+ }
+ if(dir.r){
+ r = runemalloc(dir.nr+n+1);
+ runemove(r, dir.r, dir.nr);
+ free(dir.r);
+ if(dir.nr>0 && r[dir.nr]!='/' && n>0 && arg[0]!='/')
+ r[dir.nr++] = '/';
+ runemove(r+dir.nr, arg, n);
+ n += dir.nr;
+ }else{
+ r = runemalloc(n+1);
+ runemove(r, arg, n);
+ }
+ }
+ s = runetobyte(r, n);
+ free(r);
+ if(strlen(s) == 0){
+ free(s);
+ s = nil;
+ }
+ return s;
+}
+
+void
+zeroxx(Text *et, Text *t, Text*, int, int, Rune*, int)
+{
+ Window *nw;
+ int c, locked;
+
+ 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, 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;
+
+ if(flag1)
+ if(et==nil || et->w==nil)
+ return;
+ if(!et->w->isdir && (et->w->body.file->nc>0 && !winclean(et->w, TRUE)))
+ return;
+ w = et->w;
+ t = &w->body;
+ name = getname(t, argt, arg, narg, FALSE);
+ if(name == nil){
+ warning(nil, "no file name\n");
+ return;
+ }
+ if(t->file->ntext>1){
+ d = dirstat(name);
+ isdir = (d!=nil && (d->qid.type & QTDIR));
+ free(d);
+ if(isdir){
+ warning(nil, "%s is a directory; can't read with multiple windows on it\n", name);
+ return;
+ }
+ }
+ addr = emalloc((t->file->ntext)*sizeof(TextAddr));
+ for(i=0; i<t->file->ntext; i++) {
+ a = &addr[i];
+ u = t->file->text[i];
+ a->lorigin = nlcount(u, 0, u->org, &a->rorigin);
+ a->lq0 = nlcount(u, 0, u->q0, &a->rq0);
+ a->lq1 = nlcount(u, u->q0, u->q1, &a->rq1);
+ }
+ r = bytetorune(name, &n);
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ /* second and subsequent calls with zero an already empty buffer, but OK */
+ textreset(u);
+ windirfree(u->w);
+ }
+ samename = runeeq(r, n, t->file->name, t->file->nname);
+ textload(t, 0, name, samename);
+ if(samename){
+ t->file->mod = FALSE;
+ dirty = FALSE;
+ }else{
+ t->file->mod = TRUE;
+ dirty = TRUE;
+ }
+ for(i=0; i<t->file->ntext; i++)
+ t->file->text[i]->w->dirty = dirty;
+ free(name);
+ free(r);
+ winsettag(w);
+ t->file->unread = FALSE;
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ textsetselect(&u->w->tag, u->w->tag.file->nc, u->w->tag.file->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");
+}
+
+void
+putfile(File *f, int q0, int q1, Rune *namer, int nname)
+{
+ uint n, m;
+ Rune *r;
+ char *s, *name, *p;
+ int i, fd, q;
+ Dir *d, *d1;
+ Window *w;
+ Plumbmsg *pm;
+ int isapp;
+
+ w = f->curtext->w;
+ name = runetobyte(namer, nname);
+ d = dirstat(name);
+ if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
+ /* f->mtime+1 because when talking over NFS it's often off by a second */
+ if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime+1<d->mtime){
+ f->dev = d->dev;
+ f->qidpath = d->qid.path;
+ f->mtime = d->mtime;
+ if(f->unread)
+ warning(nil, "%s not written; file already exists\n", name);
+ else
+ warning(nil, "%s modified%s%s since last read\n", name, d->muid[0]?" by ":"", d->muid);
+ goto Rescue1;
+ }
+ }
+ fd = create(name, OWRITE, 0666);
+ if(fd < 0){
+ warning(nil, "can't create file %s: %r\n", name);
+ goto Rescue1;
+ }
+ r = fbufalloc();
+ s = fbufalloc();
+ free(d);
+ d = dirfstat(fd);
+ isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
+ if(isapp){
+ warning(nil, "%s not written; file is append only\n", name);
+ goto Rescue2;
+ }
+
+ for(q=q0; q<q1; q+=n){
+ n = q1 - q;
+ if(n > (BUFSIZE-1)/UTFmax)
+ n = (BUFSIZE-1)/UTFmax;
+ bufread(f, q, r, n);
+ m = snprint(s, BUFSIZE, "%.*S", n, r);
+ if(write(fd, s, m) != m){
+ warning(nil, "can't write file %s: %r\n", name);
+ goto Rescue2;
+ }
+ }
+ if(runeeq(namer, nname, f->name, f->nname)){
+ if(q0!=0 || q1!=f->nc){
+ f->mod = TRUE;
+ w->dirty = TRUE;
+ f->unread = TRUE;
+ }else{
+ d1 = dirfstat(fd);
+ if(d1 != nil){
+ free(d);
+ d = d1;
+ }
+ f->qidpath = d->qid.path;
+ f->dev = d->dev;
+ f->mtime = d->mtime;
+ f->mod = FALSE;
+ w->dirty = FALSE;
+ f->unread = FALSE;
+ }
+ for(i=0; i<f->ntext; i++){
+ f->text[i]->w->putseq = f->seq;
+ f->text[i]->w->dirty = w->dirty;
+ }
+ }
+ if(plumbsendfd >= 0){
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("acme");
+ pm->dst = estrdup("put");
+ pm->wdir = estrdup(name);
+ if(p = strrchr(pm->wdir, '/'))
+ *p = '\0';
+ pm->type = estrdup("text");
+ pm->attr = nil;
+ pm->data = estrdup(name);
+ pm->ndata = strlen(pm->data);
+ if(pm->ndata < messagesize-1024)
+ plumbsend(plumbsendfd, pm);
+ else
+ plumbfree(pm);
+ }
+ fbuffree(s);
+ fbuffree(r);
+ free(d);
+ free(namer);
+ free(name);
+ close(fd);
+ winsettag(w);
+ return;
+
+ Rescue2:
+ fbuffree(s);
+ fbuffree(r);
+ close(fd);
+ /* fall through */
+
+ Rescue1:
+ free(d);
+ free(namer);
+ free(name);
+}
+
+void
+put(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ int nname;
+ Rune *namer;
+ Window *w;
+ File *f;
+ char *name;
+
+ 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;
+ }
+ namer = bytetorune(name, &nname);
+ putfile(f, 0, f->nc, namer, nname);
+ xfidlog(w, "put");
+ free(name);
+}
+
+void
+dump(Text *, Text *, Text *argt, int isdump, int, Rune *arg, int narg)
+{
+ char *name;
+
+ 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*, int dosnarf, int docut, Rune*, int)
+{
+ uint q0, q1, n, locked, c;
+ Rune *r;
+
+ /*
+ * 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, q0, r, n);
+ bufinsert(&snarfbuf, snarfbuf.nc, r, n);
+ q0 += n;
+ }
+ fbuffree(r);
+ putsnarf();
+ }
+ 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*, int selectall, int tobody, Rune*, int)
+{
+ int c;
+ uint q, q0, q1, n;
+ Rune *r;
+
+ /* 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;
+
+ getsnarf();
+ 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, int, Rune *arg, int narg)
+{
+ Rune *r;
+ int n;
+
+ 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, t->q0, r, n);
+ }
+ search(t, r, n);
+ free(r);
+ }
+}
+
+void
+sendx(Text *et, Text *t, Text*, int, int, Rune*, int)
+{
+ 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->nc, t->file->nc);
+ paste(t, t, nil, TRUE, TRUE, nil, 0);
+ if(textreadc(t, t->file->nc-1) != '\n'){
+ textinsert(t, t->file->nc, L"\n", 1, TRUE);
+ textsetselect(t, t->file->nc, t->file->nc);
+ }
+}
+
+void
+edit(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ Rune *r;
+ int len;
+
+ 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
+exit(Text*, Text*, Text*, int, int, Rune*, int)
+{
+ if(rowclean(&row)){
+ sendul(cexit, 0);
+ threadexits(nil);
+ }
+}
+
+void
+putall(Text*, Text*, Text*, int, int, Rune*, int)
+{
+ int i, j, e;
+ Window *w;
+ Column *c;
+ char *a;
+
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c->nw; j++){
+ w = c->w[j];
+ if(w->isscratch || w->isdir || w->body.file->nname==0)
+ continue;
+ if(w->nopen[QWevent] > 0)
+ continue;
+ a = runetobyte(w->body.file->name, w->body.file->nname);
+ e = access(a, 0);
+ if(w->body.file->mod || w->body.ncache)
+ if(e < 0)
+ warning(nil, "no auto-Put of %s: %r\n", a);
+ else{
+ wincommit(w, &w->body);
+ put(&w->body, nil, nil, XXX, XXX, nil, 0);
+ }
+ free(a);
+ }
+ }
+}
+
+
+void
+id(Text *et, Text*, Text*, int, int, Rune*, int)
+{
+ if(et && et->w)
+ warning(nil, "/mnt/acme/%d/\n", et->w->id);
+}
+
+void
+local(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ char *a, *aa;
+ Runestr dir;
+
+ 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
+kill(Text*, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ Rune *a, *cmd, *r;
+ int na;
+
+ getarg(argt, FALSE, FALSE, &r, &na);
+ if(r)
+ kill(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);
+ }
+}
+
+void
+fontx(Text *et, Text *t, Text *argt, int, int, Rune *arg, int narg)
+{
+ Rune *a, *r, *flag, *file;
+ int na, nf;
+ char *aa;
+ Reffont *newfont;
+ Dirlist *dp;
+ int i, fix;
+
+ 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, L"fix", 3) || runeeq(r, narg-na, L"var", 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, L"fix", 3) || runeeq(r, na, L"var", 3)){
+ free(flag);
+ flag = r;
+ }else{
+ free(file);
+ file = r;
+ nf = na;
+ }
+ fix = 1;
+ if(flag)
+ fix = runeeq(flag, runestrlen(flag), L"fix", 3);
+ else if(file == nil){
+ newfont = rfget(FALSE, FALSE, FALSE, nil);
+ if(newfont)
+ fix = strcmp(newfont->f->name, t->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->font = newfont->f;
+ frinittick(t);
+ if(t->w->isdir){
+ t->all.min.x++; /* force recolumnation; disgusting! */
+ for(i=0; i<t->w->ndl; i++){
+ dp = t->w->dlp[i];
+ aa = runetobyte(dp->r, dp->nr);
+ dp->wid = stringwidth(newfont->f, aa);
+ free(aa);
+ }
+ }
+ /* avoid shrinking of window due to quantization */
+ colgrow(t->w->col, t->w, -1);
+ }
+ free(file);
+ free(flag);
+}
+
+void
+incl(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ Rune *a, *r;
+ Window *w;
+ int na, n, len;
+
+ 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");
+ }
+}
+
+enum {
+ IGlobal = -2,
+ IError = -1,
+};
+
+static int
+indentval(Rune *s, int n, int type)
+{
+ static char *strs[] = {
+ [SPACESINDENT] "Spaces",
+ [AUTOINDENT] "Indent",
+ };
+
+ if(n < 2)
+ return IError;
+ if(runestrncmp(s, L"ON", n) == 0){
+ globalindent[type] = TRUE;
+ warning(nil, "%s ON\n", strs[type]);
+ return IGlobal;
+ }
+ if(runestrncmp(s, L"OFF", n) == 0){
+ globalindent[type] = FALSE;
+ warning(nil, "%s OFF\n", strs[type]);
+ return IGlobal;
+ }
+ if(runestrncmp(s, L"on", n) == 0)
+ return TRUE;
+ if(runestrncmp(s, L"off", n) == 0)
+ return FALSE;
+ return IError;
+}
+
+static void
+fixindent(Window *w, void *v)
+{
+ int t = *(int*)v;
+ w->indent[t] = globalindent[t];
+}
+
+void
+indent(Text *et, Text*, Text *argt, int type, int, Rune *arg, int narg)
+{
+ Rune *a, *r;
+ Window *w;
+ int na, len, ival;
+
+ w = nil;
+ if(et!=nil && et->w!=nil)
+ w = et->w;
+ ival = IError;
+ getarg(argt, FALSE, TRUE, &r, &len);
+ if(r!=nil && len>0)
+ ival = indentval(r, len, type);
+ else{
+ a = findbl(arg, narg, &na);
+ if(a != arg)
+ ival = indentval(arg, narg-na, type);
+ }
+ if(ival == IGlobal)
+ allwindows(fixindent, &type);
+ else if(w != nil && ival >= 0)
+ w->indent[type] = ival;
+}
+
+void
+tab(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
+{
+ Rune *a, *r;
+ Window *w;
+ int na, len, tab;
+ char *p;
+
+ if(et==nil || et->w==nil)
+ return;
+ w = et->w;
+ getarg(argt, FALSE, TRUE, &r, &len);
+ tab = 0;
+ if(r!=nil && len>0){
+ p = runetobyte(r, len);
+ if('0'<=p[0] && p[0]<='9')
+ tab = atoi(p);
+ free(p);
+ }else{
+ a = findbl(arg, narg, &na);
+ if(a != arg){
+ p = runetobyte(arg, narg-na);
+ if('0'<=p[0] && p[0]<='9')
+ tab = atoi(p);
+ free(p);
+ }
+ }
+ if(tab > 0){
+ if(w->body.tabstop != tab){
+ w->body.tabstop = tab;
+ winresize(w, w->r, 1);
+ }
+ }else
+ warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop);
+}
+
+void
+runproc(void *argvp)
+{
+ /* args: */
+ Window *win;
+ char *s;
+ Rune *rdir;
+ int ndir;
+ int newns;
+ char *argaddr;
+ char *arg;
+ Command *c;
+ Channel *cpid;
+ int iseditcmd;
+ /* end of args */
+ char *e, *t, *name, *filename, *dir, **av, *news;
+ Rune r, **incl;
+ int ac, w, inarg, i, n, fd, nincl, winid;
+ int pipechar;
+ char buf[512];
+ void **argv;
+
+ 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(rdir != nil){
+ dir = runetobyte(rdir, ndir);
+ chdir(dir); /* ignore error: probably app. window */
+ free(dir);
+ }
+ if(newns){
+ nincl = 0;
+ incl = nil;
+ if(win){
+ filename = smprint("%.*S", win->body.file->nname, win->body.file->name);
+ nincl = win->nincl;
+ if(nincl > 0){
+ incl = emalloc(nincl*sizeof(Rune*));
+ for(i=0; i<nincl; i++){
+ n = runestrlen(win->incl[i]);
+ incl[i] = runemalloc(n+1);
+ runemove(incl[i], win->incl[i], n);
+ }
+ }
+ winid = win->id;
+ }else{
+ filename = nil;
+ winid = 0;
+ if(activewin)
+ winid = activewin->id;
+ }
+ rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
+ sprint(buf, "%d", winid);
+ putenv("winid", buf);
+
+ if(filename){
+ putenv("%", filename);
+ free(filename);
+ }
+ c->md = fsysmount(rdir, ndir, incl, nincl);
+ if(c->md == nil){
+ fprint(2, "child: can't mount /dev/cons: %r\n");
+ threadexits("mount");
+ }
+ close(0);
+ if(winid>0 && (pipechar=='|' || pipechar=='>')){
+ sprint(buf, "/mnt/acme/%d/rdsel", winid);
+ open(buf, OREAD);
+ }else
+ open("/dev/null", OREAD);
+ close(1);
+ if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
+ if(iseditcmd){
+ if(winid > 0)
+ sprint(buf, "/mnt/acme/%d/editout", winid);
+ else
+ sprint(buf, "/mnt/acme/editout");
+ }else
+ sprint(buf, "/mnt/acme/%d/wrsel", winid);
+ open(buf, OWRITE);
+ close(2);
+ open("/dev/cons", OWRITE);
+ }else{
+ open("/dev/cons", OWRITE);
+ dup(1, 2);
+ }
+ }else{
+ rfork(RFFDG|RFNOTEG);
+ fsysclose();
+ close(0);
+ open("/dev/null", OREAD);
+ close(1);
+ open(acmeerrorfile, OWRITE);
+ dup(1, 2);
+ }
+
+ if(win)
+ winclose(win);
+
+ if(argaddr)
+ putenv("acmeaddr", argaddr);
+ if(strlen(t) > sizeof buf-10) /* may need to print into stack */
+ goto Hard;
+ inarg = FALSE;
+ for(e=t; *e; e+=w){
+ w = chartorune(&r, e);
+ if(r==' ' || r=='\t')
+ continue;
+ if(r < ' ')
+ goto Hard;
+ if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r))
+ goto Hard;
+ inarg = TRUE;
+ }
+ if(!inarg)
+ goto Fail;
+
+ ac = 0;
+ av = nil;
+ inarg = FALSE;
+ for(e=t; *e; e+=w){
+ w = chartorune(&r, e);
+ if(r==' ' || r=='\t'){
+ inarg = FALSE;
+ *e = 0;
+ continue;
+ }
+ if(!inarg){
+ inarg = TRUE;
+ av = realloc(av, (ac+1)*sizeof(char**));
+ av[ac++] = e;
+ }
+ }
+ av = realloc(av, (ac+2)*sizeof(char**));
+ av[ac++] = arg;
+ av[ac] = nil;
+ c->av = av;
+ procexec(cpid, av[0], av);
+ e = av[0];
+ if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
+ goto Fail;
+ if(cputype){
+ sprint(buf, "%s/%s", cputype, av[0]);
+ procexec(cpid, buf, av);
+ }
+ sprint(buf, "/bin/%s", av[0]);
+ procexec(cpid, buf, av);
+ goto Fail;
+
+Hard:
+
+ /*
+ * ugly: set path = (. $cputype /bin)
+ * should honor $path if unusual.
+ */
+ if(cputype){
+ n = 0;
+ memmove(buf+n, ".", 2);
+ n += 2;
+ i = strlen(cputype)+1;
+ memmove(buf+n, cputype, i);
+ n += i;
+ memmove(buf+n, "/bin", 5);
+ n += 5;
+ fd = create("/env/path", OWRITE, 0666);
+ write(fd, buf, n);
+ close(fd);
+ }
+
+ if(arg){
+ news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1);
+ if(news){
+ sprint(news, "%s '%s'", t, arg); /* BUG: what if quote in arg? */
+ free(s);
+ t = news;
+ c->text = news;
+ }
+ }
+ procexecl(cpid, "/bin/rc", "rc", "-c", t, nil);
+
+ Fail:
+ /* procexec hasn't happened, so send a zero */
+ sendul(cpid, 0);
+ threadexits(nil);
+}
+
+void
+runwaittask(void *v)
+{
+ Command *c;
+ Channel *cpid;
+ void **a;
+
+ threadsetname("runwaittask");
+ a = v;
+ c = a[0];
+ cpid = a[1];
+ free(a);
+ do
+ c->pid = recvul(cpid);
+ while(c->pid == ~0);
+ free(c->av);
+ if(c->pid != 0) /* successful exec */
+ sendp(ccommand, c);
+ else{
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->name);
+ free(c->text);
+ free(c);
+ }
+ chanfree(cpid);
+}
+
+void
+run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd)
+{
+ void **arg;
+ Command *c;
+ Channel *cpid;
+
+ if(s == nil)
+ return;
+
+ arg = emalloc(10*sizeof(void*));
+ c = emalloc(sizeof *c);
+ cpid = chancreate(sizeof(ulong), 0);
+ arg[0] = win;
+ arg[1] = s;
+ arg[2] = rdir;
+ arg[3] = (void*)ndir;
+ arg[4] = (void*)newns;
+ arg[5] = argaddr;
+ arg[6] = xarg;
+ arg[7] = c;
+ arg[8] = cpid;
+ arg[9] = (void*)iseditcmd;
+ proccreate(runproc, arg, STACK);
+ /* mustn't block here because must be ready to answer mount() call in run() */
+ arg = emalloc(2*sizeof(void*));
+ arg[0] = c;
+ arg[1] = cpid;
+ threadcreate(runwaittask, arg, STACK);
+}
--- /dev/null
+++ b/file.c
@@ -1,0 +1,310 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+/*
+ * Structure of Undo list:
+ * The Undo structure follows any associated data, so the list
+ * can be read backwards: read the structure, then read whatever
+ * data is associated (insert string, file name) and precedes it.
+ * The structure includes the previous value of the modify bit
+ * and a sequence number; successive Undo structures with the
+ * same sequence number represent simultaneous changes.
+ */
+
+typedef struct Undo Undo;
+struct Undo
+{
+ short type; /* Delete, Insert, Filename */
+ short mod; /* modify bit */
+ uint seq; /* sequence number */
+ uint p0; /* location of change (unused in f) */
+ uint n; /* # runes in string or file name */
+};
+
+enum
+{
+ Undosize = sizeof(Undo)/sizeof(Rune),
+};
+
+File*
+fileaddtext(File *f, Text *t)
+{
+ if(f == nil){
+ f = emalloc(sizeof(File));
+ f->unread = TRUE;
+ }
+ f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
+ f->text[f->ntext++] = t;
+ f->curtext = t;
+ return f;
+}
+
+void
+filedeltext(File *f, Text *t)
+{
+ int i;
+
+ for(i=0; i<f->ntext; i++)
+ if(f->text[i] == t)
+ goto Found;
+ error("can't find text in filedeltext");
+
+ Found:
+ f->ntext--;
+ if(f->ntext == 0){
+ fileclose(f);
+ return;
+ }
+ memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
+ if(f->curtext == t)
+ f->curtext = f->text[0];
+}
+
+void
+fileinsert(File *f, uint p0, Rune *s, uint ns)
+{
+ if(p0 > f->nc)
+ error("internal error: fileinsert");
+ if(f->seq > 0)
+ fileuninsert(f, &f->delta, p0, ns);
+ bufinsert(f, 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->nc && p1<=f->nc))
+ error("internal error: filedelete");
+ if(f->seq > 0)
+ fileundelete(f, &f->delta, p0, p1);
+ bufdelete(f, p0, p1);
+ if(p1 > p0)
+ f->mod = TRUE;
+}
+
+void
+fileundelete(File *f, Buffer *delta, uint p0, uint p1)
+{
+ Undo u;
+ Rune *buf;
+ uint i, n;
+
+ /* undo a deletion by inserting */
+ u.type = Insert;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = p0;
+ u.n = p1-p0;
+ buf = fbufalloc();
+ for(i=p0; i<p1; i+=n){
+ n = p1 - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(f, i, buf, n);
+ bufinsert(delta, delta->nc, buf, n);
+ }
+ fbuffree(buf);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+
+}
+
+void
+filesetname(File *f, Rune *name, int n)
+{
+ if(f->seq > 0)
+ fileunsetname(f, &f->delta);
+ free(f->name);
+ f->name = runemalloc(n);
+ runemove(f->name, name, n);
+ f->nname = n;
+ f->unread = TRUE;
+}
+
+void
+fileunsetname(File *f, Buffer *delta)
+{
+ Undo u;
+
+ /* undo a file name change by restoring old name */
+ u.type = Filename;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = 0; /* unused */
+ u.n = f->nname;
+ if(f->nname)
+ bufinsert(delta, delta->nc, f->name, f->nname);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+uint
+fileload(File *f, uint p0, int fd, int *nulls)
+{
+ if(f->seq > 0)
+ error("undo in file.load unimplemented");
+ return bufload(f, p0, fd, nulls);
+}
+
+/* return sequence number of pending redo */
+uint
+fileredoseq(File *f)
+{
+ Undo u;
+ Buffer *delta;
+
+ delta = &f->epsilon;
+ if(delta->nc == 0)
+ return 0;
+ bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize);
+ return u.seq;
+}
+
+void
+fileundo(File *f, int isundo, uint *q0p, uint *q1p)
+{
+ Undo u;
+ Rune *buf;
+ uint i, j, n, up;
+ uint stop;
+ Buffer *delta, *epsilon;
+
+ if(isundo){
+ /* undo; reverse delta onto epsilon, seq decreases */
+ delta = &f->delta;
+ epsilon = &f->epsilon;
+ stop = f->seq;
+ }else{
+ /* redo; reverse epsilon onto delta, seq increases */
+ delta = &f->epsilon;
+ epsilon = &f->delta;
+ stop = 0; /* don't know yet */
+ }
+
+ buf = fbufalloc();
+ while(delta->nc > 0){
+ up = delta->nc-Undosize;
+ bufread(delta, up, (Rune*)&u, Undosize);
+ if(isundo){
+ if(u.seq < stop){
+ f->seq = u.seq;
+ goto Return;
+ }
+ }else{
+ if(stop == 0)
+ stop = u.seq;
+ if(u.seq > stop)
+ goto Return;
+ }
+ switch(u.type){
+ default:
+ fprint(2, "undo: 0x%ux\n", u.type);
+ abort();
+ break;
+
+ case Delete:
+ f->seq = u.seq;
+ fileundelete(f, epsilon, u.p0, u.p0+u.n);
+ f->mod = u.mod;
+ bufdelete(f, u.p0, u.p0+u.n);
+ for(j=0; j<f->ntext; j++)
+ textdelete(f->text[j], u.p0, u.p0+u.n, FALSE);
+ *q0p = u.p0;
+ *q1p = u.p0;
+ break;
+
+ case Insert:
+ f->seq = u.seq;
+ fileuninsert(f, epsilon, u.p0, u.n);
+ f->mod = u.mod;
+ up -= u.n;
+ for(i=0; i<u.n; i+=n){
+ n = u.n - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(delta, up+i, buf, n);
+ bufinsert(f, u.p0+i, buf, n);
+ for(j=0; j<f->ntext; j++)
+ textinsert(f->text[j], u.p0+i, buf, n, FALSE);
+ }
+ *q0p = u.p0;
+ *q1p = u.p0+u.n;
+ break;
+
+ case Filename:
+ f->seq = u.seq;
+ fileunsetname(f, epsilon);
+ f->mod = u.mod;
+ up -= u.n;
+ free(f->name);
+ if(u.n == 0)
+ f->name = nil;
+ else
+ f->name = runemalloc(u.n);
+ bufread(delta, up, f->name, u.n);
+ f->nname = u.n;
+ break;
+ }
+ bufdelete(delta, up, delta->nc);
+ }
+ if(isundo)
+ f->seq = 0;
+ Return:
+ fbuffree(buf);
+}
+
+void
+filereset(File *f)
+{
+ bufreset(&f->delta);
+ bufreset(&f->epsilon);
+ f->seq = 0;
+}
+
+void
+fileclose(File *f)
+{
+ free(f->name);
+ f->nname = 0;
+ f->name = nil;
+ free(f->text);
+ f->ntext = 0;
+ f->text = nil;
+ bufclose(f);
+ 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;
+}
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,96 @@
+#pragma varargck argpos warning 2
+
+void warning(Mntdir*, char*, ...);
+
+#define fbufalloc() emalloc(BUFSIZE)
+#define fbuffree(x) free(x)
+
+void plumblook(Plumbmsg*m);
+void plumbshow(Plumbmsg*m);
+void putsnarf(void);
+void getsnarf(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);
+char* getname(Text*, Text*, Rune*, int, 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*);
+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);
+int isspace(Rune);
+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 fsysincid(Mntdir*);
+void fsysdelid(Mntdir*);
+Xfid* respond(Xfid*, Fcall*, char*);
+int rxcompile(Rune*);
+int rgetc(void*, uint);
+int tgetc(void*, uint);
+int isaddrc(int);
+int isregexc(int);
+void *emalloc(uint);
+void *erealloc(void*, uint);
+char *estrdup(char*);
+Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*);
+int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int rxbexecute(Text*, uint, Rangeset*);
+Window* makenewwindow(Text *t);
+int expand(Text*, uint, uint, Expand*);
+Rune* skipbl(Rune*, int, int*);
+Rune* findbl(Rune*, int, int*);
+char* edittext(Window*, int, Rune*, int);
+void flushwarnings(void);
+long nlcount(Text*, long, long, long*);
+long nlcounttopos(Text*, long, long, long);
+
+#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))
--- /dev/null
+++ b/fsys.c
@@ -1,0 +1,749 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static int cfd;
+static int sfd;
+
+enum
+{
+ Nhash = 16,
+ DEBUG = 0
+};
+
+static Fid *fids[Nhash];
+
+Fid *newfid(int);
+
+static Xfid* fsysflush(Xfid*, Fid*);
+static Xfid* fsysauth(Xfid*, Fid*);
+static Xfid* fsysversion(Xfid*, Fid*);
+static Xfid* fsysattach(Xfid*, Fid*);
+static Xfid* fsyswalk(Xfid*, Fid*);
+static Xfid* fsysopen(Xfid*, Fid*);
+static Xfid* fsyscreate(Xfid*, Fid*);
+static Xfid* fsysread(Xfid*, Fid*);
+static Xfid* fsyswrite(Xfid*, Fid*);
+static Xfid* fsysclunk(Xfid*, Fid*);
+static Xfid* fsysremove(Xfid*, Fid*);
+static Xfid* fsysstat(Xfid*, Fid*);
+static Xfid* fsyswstat(Xfid*, Fid*);
+
+Xfid* (*fcall[Tmax])(Xfid*, Fid*) =
+{
+ [Tflush] = fsysflush,
+ [Tversion] = fsysversion,
+ [Tauth] = fsysauth,
+ [Tattach] = fsysattach,
+ [Twalk] = fsyswalk,
+ [Topen] = fsysopen,
+ [Tcreate] = fsyscreate,
+ [Tread] = fsysread,
+ [Twrite] = fsyswrite,
+ [Tclunk] = fsysclunk,
+ [Tremove]= fsysremove,
+ [Tstat] = fsysstat,
+ [Twstat] = fsyswstat,
+};
+
+char Eperm[] = "permission denied";
+char Eexist[] = "file does not exist";
+char Enotdir[] = "not a directory";
+
+Dirtab dirtab[]=
+{
+ { ".", QTDIR, Qdir, 0500|DMDIR },
+ { "acme", QTDIR, Qacme, 0500|DMDIR },
+ { "cons", QTFILE, Qcons, 0600 },
+ { "consctl", QTFILE, Qconsctl, 0000 },
+ { "draw", QTDIR, Qdraw, 0000|DMDIR }, /* to suppress graphics progs started in acme */
+ { "editout", QTFILE, Qeditout, 0200 },
+ { "index", QTFILE, Qindex, 0400 },
+ { "label", QTFILE, Qlabel, 0600 },
+ { "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;
+ 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";
+int clockfd;
+static int closing = 0;
+int messagesize = Maxblock+IOHDRSZ; /* good start */
+
+void fsysproc(void *);
+
+void
+fsysinit(void)
+{
+ int p[2];
+
+ if(pipe(p) < 0)
+ error("can't create pipe");
+ cfd = p[0];
+ sfd = p[1];
+ fmtinstall('F', fcallfmt);
+ clockfd = open("/dev/time", OREAD|OCEXEC);
+ user = getuser();
+ proccreate(fsysproc, nil, STACK);
+}
+
+void
+fsysproc(void *)
+{
+ int n;
+ Xfid *x;
+ Fid *f;
+ Fcall t;
+ uchar *buf;
+
+ 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) != n)
+ error("convert error in convM2S");
+ if(DEBUG)
+ fprint(2, "%F\n", &x->Fcall);
+ if(fcall[x->type] == nil)
+ x = respond(x, &t, "bad fcall type");
+ else{
+ switch(x->type){
+ case Tversion:
+ case Tauth:
+ case Tflush:
+ f = nil;
+ break;
+ case Tattach:
+ f = newfid(x->fid);
+ break;
+ default:
+ f = newfid(x->fid);
+ if(!f->busy){
+ x->f = f;
+ x = respond(x, &t, "fid not in use");
+ continue;
+ }
+ break;
+ }
+ x->f = f;
+ x = (*fcall[x->type])(x, f);
+ }
+ }
+}
+
+Mntdir*
+fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl)
+{
+ Mntdir *m;
+ int id;
+
+ qlock(&mnt);
+ 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);
+ return m;
+}
+
+void
+fsysincid(Mntdir *m)
+{
+ qlock(&mnt);
+ m->ref++;
+ qunlock(&mnt);
+}
+
+void
+fsysdelid(Mntdir *idm)
+{
+ Mntdir *m, *prev;
+ int i;
+ char buf[64];
+
+ if(idm == nil)
+ return;
+ qlock(&mnt);
+ if(--idm->ref > 0){
+ qunlock(&mnt);
+ return;
+ }
+ prev = nil;
+ for(m=mnt.md; m; m=m->next){
+ if(m == idm){
+ if(prev)
+ prev->next = m->next;
+ else
+ mnt.md = m->next;
+ for(i=0; i<m->nincl; i++)
+ free(m->incl[i]);
+ free(m->incl);
+ free(m->dir);
+ free(m);
+ qunlock(&mnt);
+ return;
+ }
+ prev = m;
+ }
+ qunlock(&mnt);
+ 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)
+{
+ char buf[256];
+ Mntdir *m;
+
+ /* close server side so don't hang if acme is half-exited */
+ close(sfd);
+ m = fsysaddid(dir, ndir, incl, nincl);
+ sprint(buf, "%d", m->id);
+ if(mount(cfd, -1, "/mnt/acme", MREPL, buf) == -1){
+ fsysdelid(m);
+ return nil;
+ }
+ bind("/mnt/acme", "/mnt/wsys", MREPL);
+ if(bind("/mnt/acme", "/dev", MBEFORE) == -1){
+ fsysdelid(m);
+ return nil;
+ }
+ return m;
+}
+
+void
+fsysclose(void)
+{
+ closing = 1;
+ close(cfd);
+ close(sfd);
+}
+
+Xfid*
+respond(Xfid *x, Fcall *t, char *err)
+{
+ int n;
+
+ if(err){
+ t->type = Rerror;
+ t->ename = err;
+ }else
+ t->type = x->type+1;
+ t->fid = x->fid;
+ t->tag = x->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*)
+{
+ Fcall t;
+
+ if(x->msize < 256)
+ return respond(x, &t, "version: message size too small");
+ messagesize = x->msize;
+ t.msize = messagesize;
+ t.version = "9P2000";
+ if(strncmp(x->version, "9P", 2) != 0)
+ t.version = "unknown";
+ return respond(x, &t, nil);
+}
+
+static
+Xfid*
+fsysauth(Xfid *x, Fid*)
+{
+ Fcall t;
+
+ return respond(x, &t, "acme: authentication not required");
+}
+
+static
+Xfid*
+fsysflush(Xfid *x, Fid*)
+{
+ sendp(x->c, xfidflush);
+ return nil;
+}
+
+static
+Xfid*
+fsysattach(Xfid *x, Fid *f)
+{
+ Fcall t;
+ int id;
+ Mntdir *m;
+
+ if(strcmp(x->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->aname);
+ qlock(&mnt);
+ for(m=mnt.md; m; m=m->next)
+ if(m->id == id){
+ f->mntdir = m;
+ m->ref++;
+ break;
+ }
+ if(m == nil)
+ sendp(cerr, estrdup("unknown id in attach"));
+ qunlock(&mnt);
+ 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->fid != x->newfid){
+ nf = newfid(x->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);
+ f = nf; /* walk f */
+ }
+
+ t.nwqid = 0;
+ err = nil;
+ dir = nil;
+ id = WIN(f->qid);
+ q = f->qid;
+
+ if(x->nwname > 0){
+ for(i=0; i<x->nwname; i++){
+ if((q.type & QTDIR) == 0){
+ err = Enotdir;
+ break;
+ }
+
+ if(strcmp(x->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->wname[i][j]); j++)
+ if(c<'0' || '9'<c)
+ goto Regular;
+ /* yes: it's a directory */
+ if(w) /* name has form 27/23; get out before losing w */
+ break;
+ id = atoi(x->wname[i]);
+ qlock(&row);
+ w = lookid(id, FALSE);
+ if(w == nil){
+ qunlock(&row);
+ break;
+ }
+ incref(w); /* we'll drop reference at end if there's an error */
+ path = Qdir;
+ type = QTDIR;
+ qunlock(&row);
+ dir = dirtabw;
+ goto Accept;
+
+ Regular:
+// if(FILE(f->qid) == Qacme) /* empty directory */
+// break;
+ if(strcmp(x->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);
+ 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->wname[i], d->name) == 0){
+ path = d->qid;
+ type = d->type;
+ dir = d;
+ goto Accept;
+ }
+
+ break; /* file not found */
+ }
+
+ if(i==0 && err == nil)
+ err = Eexist;
+ }
+
+ if(err!=nil || t.nwqid<x->nwname){
+ if(nf){
+ nf->busy = FALSE;
+ fsysdelid(nf->mntdir);
+ }
+ }else if(t.nwqid == x->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->mode &= ~(OTRUNC|OCEXEC);
+ /* can't execute or remove anything */
+ if(x->mode==OEXEC || (x->mode&ORCLOSE))
+ goto Deny;
+ switch(x->mode){
+ default:
+ goto Deny;
+ case OREAD:
+ m = 0400;
+ break;
+ case OWRITE:
+ m = 0200;
+ break;
+ case ORDWR:
+ m = 0600;
+ break;
+ }
+ if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
+ goto Deny;
+
+ sendp(x->c, xfidopen);
+ return nil;
+
+ Deny:
+ return respond(x, &t, Eperm);
+}
+
+static
+Xfid*
+fsyscreate(Xfid *x, Fid*)
+{
+ Fcall t;
+
+ return respond(x, &t, Eperm);
+}
+
+static
+int
+idcmp(void *a, 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->offset;
+ e = x->offset+x->count;
+ clock = getclock();
+ b = emalloc(messagesize);
+ id = WIN(f->qid);
+ n = 0;
+ if(id > 0)
+ d = dirtabw;
+ else
+ d = dirtab;
+ d++; /* first entry is '.' */
+ for(i=0; d->name!=nil && i<e; i+=len){
+ len = dostat(WIN(x->f->qid), d, b+n, x->count-n, clock);
+ if(len <= BIT16SZ)
+ break;
+ if(i >= o)
+ n += len;
+ d++;
+ }
+ if(id == 0){
+ qlock(&row);
+ nids = 0;
+ ids = nil;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(k=0; k<c->nw; k++){
+ ids = realloc(ids, (nids+1)*sizeof(int));
+ ids[nids++] = c->w[k]->id;
+ }
+ }
+ qunlock(&row);
+ qsort(ids, nids, sizeof ids[0], idcmp);
+ j = 0;
+ dt.name = buf;
+ for(; j<nids && i<e; i+=len){
+ k = ids[j];
+ sprint(dt.name, "%d", k);
+ dt.qid = QID(k, Qdir);
+ dt.type = QTDIR;
+ dt.perm = DMDIR|0700;
+ len = dostat(k, &dt, b+n, x->count-n, clock);
+ if(len == 0)
+ break;
+ if(i >= o)
+ n += len;
+ j++;
+ }
+ free(ids);
+ }
+ t.data = (char*)b;
+ t.count = n;
+ respond(x, &t, nil);
+ free(b);
+ return x;
+ }
+ sendp(x->c, xfidread);
+ return nil;
+}
+
+static
+Xfid*
+fsyswrite(Xfid *x, Fid*)
+{
+ sendp(x->c, xfidwrite);
+ return nil;
+}
+
+static
+Xfid*
+fsysclunk(Xfid *x, Fid *f)
+{
+ fsysdelid(f->mntdir);
+ sendp(x->c, xfidclose);
+ return nil;
+}
+
+static
+Xfid*
+fsysremove(Xfid *x, Fid*)
+{
+ Fcall t;
+
+ 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*)
+{
+ Fcall t;
+
+ 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()
+{
+ char buf[32];
+
+ buf[0] = '\0';
+ pread(clockfd, buf, sizeof buf, 0);
+ return atoi(buf);
+}
+
+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);
+}
--- /dev/null
+++ b/logf.c
@@ -1,0 +1,202 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <libsec.h>
+#include "dat.h"
+#include "fns.h"
+
+// State for global log file.
+typedef struct Log Log;
+struct Log
+{
+ QLock lk;
+ Rendez r;
+
+ vlong start; // msg[0] corresponds to 'start' in the global sequence of events
+
+ // queued events (nev=entries in ev, mev=capacity of p)
+ char **ev;
+ int nev;
+ int mev;
+
+ // open acme/put files that need to read events
+ Fid **f;
+ int nf;
+ int mf;
+
+ // active (blocked) reads waiting for events
+ Xfid **read;
+ int nread;
+ int mread;
+};
+
+static Log eventlog;
+
+void
+xfidlogopen(Xfid *x)
+{
+ qlock(&eventlog.lk);
+ if(eventlog.nf >= eventlog.mf) {
+ eventlog.mf = eventlog.mf*2;
+ if(eventlog.mf == 0)
+ eventlog.mf = 8;
+ eventlog.f = erealloc(eventlog.f, eventlog.mf*sizeof eventlog.f[0]);
+ }
+ eventlog.f[eventlog.nf++] = x->f;
+ x->f->logoff = eventlog.start + eventlog.nev;
+
+ qunlock(&eventlog.lk);
+}
+
+void
+xfidlogclose(Xfid *x)
+{
+ int i;
+
+ qlock(&eventlog.lk);
+ for(i=0; i<eventlog.nf; i++) {
+ if(eventlog.f[i] == x->f) {
+ eventlog.f[i] = eventlog.f[--eventlog.nf];
+ break;
+ }
+ }
+ qunlock(&eventlog.lk);
+}
+
+void
+xfidlogread(Xfid *x)
+{
+ char *p;
+ int i;
+ Fcall fc;
+
+ qlock(&eventlog.lk);
+ if(eventlog.nread >= eventlog.mread) {
+ eventlog.mread = eventlog.mread*2;
+ if(eventlog.mread == 0)
+ eventlog.mread = 8;
+ eventlog.read = erealloc(eventlog.read, eventlog.mread*sizeof eventlog.read[0]);
+ }
+ eventlog.read[eventlog.nread++] = x;
+
+ if(eventlog.r.l == nil)
+ eventlog.r.l = &eventlog.lk;
+ x->flushed = FALSE;
+ while(x->f->logoff >= eventlog.start+eventlog.nev && !x->flushed)
+ rsleep(&eventlog.r);
+
+ for(i=0; i<eventlog.nread; i++) {
+ if(eventlog.read[i] == x) {
+ eventlog.read[i] = eventlog.read[--eventlog.nread];
+ break;
+ }
+ }
+
+ if(x->flushed) {
+ qunlock(&eventlog.lk);
+ return;
+ }
+
+ i = x->f->logoff - eventlog.start;
+ p = estrdup(eventlog.ev[i]);
+ x->f->logoff++;
+ qunlock(&eventlog.lk);
+
+ fc.data = p;
+ fc.count = strlen(p);
+ respond(x, &fc, nil);
+ free(p);
+}
+
+void
+xfidlogflush(Xfid *x)
+{
+ int i;
+ Xfid *rx;
+
+ qlock(&eventlog.lk);
+ for(i=0; i<eventlog.nread; i++) {
+ rx = eventlog.read[i];
+ if(rx->tag == x->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
+ *
+ * op == "focus" for window focus change
+ * - called from mousethread
+ */
+void
+xfidlog(Window *w, char *op)
+{
+ int i, n;
+ vlong min;
+ File *f;
+ char *name;
+
+ qlock(&eventlog.lk);
+ if(eventlog.nev >= eventlog.mev) {
+ // Remove and free any entries that all readers have read.
+ min = eventlog.start + eventlog.nev;
+ for(i=0; i<eventlog.nf; i++) {
+ if(min > eventlog.f[i]->logoff)
+ min = eventlog.f[i]->logoff;
+ }
+ if(min > eventlog.start) {
+ n = min - eventlog.start;
+ for(i=0; i<n; i++)
+ free(eventlog.ev[i]);
+ eventlog.nev -= n;
+ eventlog.start += n;
+ memmove(eventlog.ev, eventlog.ev+n, eventlog.nev*sizeof eventlog.ev[0]);
+ }
+
+ // Otherwise grow.
+ if(eventlog.nev >= eventlog.mev) {
+ eventlog.mev = eventlog.mev*2;
+ if(eventlog.mev == 0)
+ eventlog.mev = 8;
+ eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]);
+ }
+ }
+ f = w->body.file;
+ name = runetobyte(f->name, f->nname);
+ if(name == nil)
+ name = estrdup("");
+ eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name);
+ free(name);
+ if(eventlog.r.l == nil)
+ eventlog.r.l = &eventlog.lk;
+ rwakeupall(&eventlog.r);
+ qunlock(&eventlog.lk);
+}
--- /dev/null
+++ b/look.c
@@ -1,0 +1,737 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+Window* openfile(Text*, Expand*);
+
+int nuntitled;
+
+void
+look3(Text *t, uint q0, uint q1, int external)
+{
+ int n, c, f, expanded;
+ Text *ct;
+ Expand e;
+ Rune *r;
+ uint p;
+ Plumbmsg *m;
+ Runestr dir;
+ char buf[32];
+
+ ct = seltext;
+ if(ct == nil)
+ seltext = t;
+ expanded = expand(t, q0, q1, &e);
+ if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
+ /* send alphanumeric expansion to external client */
+ if(expanded == FALSE)
+ return;
+ f = 0;
+ if((e.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, 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.at->file, e.a0, r+e.nname+1, e.a1-e.a0);
+ }
+ }else{
+ n = e.q1 - e.q0;
+ r = runemalloc(n);
+ bufread(t->file, e.q0, r, n);
+ }
+ f &= ~2;
+ if(n <= EVENTSIZE)
+ winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
+ else
+ winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
+ free(r);
+ goto Return;
+ }
+ if(plumbsendfd >= 0){
+ /* send whitespace-delimited word to plumber */
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup("acme");
+ m->dst = nil;
+ dir = dirname(t, nil, 0);
+ if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
+ free(dir.r);
+ dir.r = nil;
+ dir.nr = 0;
+ }
+ if(dir.nr == 0)
+ m->wdir = estrdup(wdir);
+ else
+ m->wdir = runetobyte(dir.r, dir.nr);
+ free(dir.r);
+ m->type = estrdup("text");
+ m->attr = nil;
+ buf[0] = '\0';
+ if(q1 == q0){
+ if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ }else{
+ p = q0;
+ while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
+ q0--;
+ while(q1<t->file->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, q0, r, q1-q0);
+ m->data = runetobyte(r, q1-q0);
+ m->ndata = strlen(m->data);
+ free(r);
+ if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){
+ plumbfree(m);
+ goto Return;
+ }
+ plumbfree(m);
+ /* plumber failed to match; fall through */
+ }
+
+ /* interpret alphanumeric string ourselves */
+ if(expanded == FALSE)
+ return;
+ if(e.name || e.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, e.q0, r, n);
+ if(search(ct, r, n) && e.jump)
+ moveto(mousectl, addpt(frptofchar(ct, ct->p0), Pt(4, ct->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.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.ar = bytetorune(addr, &e.a1);
+ e.agetc = plumbgetc;
+ }
+ openfile(nil, &e);
+ free(e.name);
+ free(e.at);
+}
+
+void
+plumbshow(Plumbmsg *m)
+{
+ Window *w;
+ Rune rb[256], *r;
+ int nb, nr;
+ Runestr rs;
+ char *name, *p, namebuf[16];
+
+ w = makenewwindow(nil);
+ name = plumblookup(m->attr, "filename");
+ if(name == nil){
+ name = namebuf;
+ nuntitled++;
+ snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
+ }
+ p = nil;
+ if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
+ nb = strlen(m->wdir) + 1 + strlen(name) + 1;
+ p = emalloc(nb);
+ snprint(p, nb, "%s/%s", m->wdir, name);
+ name = p;
+ }
+ cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
+ free(p);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ r = runemalloc(m->ndata);
+ cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
+ textinsert(&w->body, 0, r, nr, TRUE);
+ free(r);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->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->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->nc){
+ q = 0;
+ around = 1;
+ nb = 0;
+ b[nb] = 0;
+ }
+ if(nb > 0){
+ c = runestrchr(b, r[0]);
+ if(c == nil){
+ q += nb;
+ nb = 0;
+ b[nb] = 0;
+ if(around && q>=ct->q1)
+ break;
+ continue;
+ }
+ q += (c-b);
+ nb -= (c-b);
+ b = c;
+ }
+ /* reload if buffer covers neither string nor rest of file */
+ if(nb<n && nb!=ct->file->nc-q){
+ nb = ct->file->nc-q;
+ if(nb >= maxn)
+ nb = maxn-1;
+ bufread(ct->file, 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)
+{
+ if(isalnum(r))
+ return TRUE;
+ if(runestrchr(L".-+/:@", 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;
+
+ 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, L"/", 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];
+ Runestr file;
+ int i;
+
+ if(objdir==nil && objtype!=nil){
+ sprint(buf, "/%s/include", objtype);
+ objdir = bytetorune(buf, &i);
+ objdir = runerealloc(objdir, i+1);
+ objdir[i] = '\0';
+ }
+
+ w = t->w;
+ if(n==0 || r[0]=='/' || w==nil)
+ goto Rescue;
+ if(n>2 && r[0]=='.' && r[1]=='/')
+ goto Rescue;
+ file.r = nil;
+ file.nr = 0;
+ for(i=0; i<w->nincl && file.r==nil; i++)
+ file = includefile(w->incl[i], r, n);
+
+ if(file.r == nil)
+ file = includefile(L"/sys/include", 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->nc;
+ if(nt == 0)
+ goto Rescue;
+ if(n>=1 && r[0]=='/')
+ goto Rescue;
+ b = runemalloc(nt+n+1);
+ bufread(t->w->tag.file, 0, b, nt);
+ slash = -1;
+ for(m=0; m<nt; m++){
+ c = b[m];
+ if(c == '/')
+ slash = m;
+ if(c==' ' || c=='\t')
+ break;
+ }
+ if(slash < 0)
+ goto Rescue;
+ runemove(b+slash+1, r, n);
+ free(r);
+ return cleanrname((Runestr){b, slash+1+n});
+
+ Rescue:
+ free(b);
+ tmp = (Runestr){r, n};
+ if(r)
+ return cleanrname(tmp);
+ return tmp;
+}
+
+int
+expandfile(Text *t, uint q0, uint q1, Expand *e)
+{
+ int i, n, nname, colon, eval;
+ uint amin, amax;
+ Rune *r, c;
+ Window *w;
+ Runestr rs;
+
+ amax = q1;
+ if(q1 == q0){
+ colon = -1;
+ while(q1<t->file->nc && isfilec(c=textreadc(t, q1))){
+ if(c == ':'){
+ colon = q1;
+ break;
+ }
+ q1++;
+ }
+ while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
+ q0--;
+ if(colon<0 && c==':')
+ colon = q0;
+ }
+ /*
+ * if it looks like it might begin file: , consume address chars after :
+ * otherwise terminate expansion at :
+ */
+ if(colon >= 0){
+ q1 = colon;
+ if(colon<t->file->nc-1 && isaddrc(textreadc(t, colon+1))){
+ q1 = colon+1;
+ while(q1<t->file->nc && isaddrc(textreadc(t, q1)))
+ q1++;
+ }
+ }
+ if(q1 > q0)
+ if(colon >= 0){ /* stop at white space */
+ for(amax=colon+1; amax<t->file->nc; amax++)
+ if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
+ break;
+ }else
+ amax = t->file->nc;
+ }
+ amin = amax;
+ e->q0 = q0;
+ e->q1 = q1;
+ n = q1-q0;
+ if(n == 0)
+ return FALSE;
+ /* see if it's a file name */
+ r = runemalloc(n);
+ bufread(t->file, q0, r, n);
+ /* first, does it have bad chars? */
+ nname = -1;
+ for(i=0; i<n; i++){
+ c = r[i];
+ if(c==':' && nname<0){
+ if(q0+i+1<t->file->nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
+ amin = q0+i;
+ else
+ goto Isntfile;
+ nname = i;
+ }
+ }
+ if(nname == -1)
+ nname = n;
+ for(i=0; i<nname; i++)
+ if(!isfilec(r[i]))
+ goto Isntfile;
+ /*
+ * See if it's a file name in <>, and turn that into an include
+ * file name if so. Should probably do it for "" too, but that's not
+ * restrictive enough syntax and checking for a #include earlier on the
+ * line would be silly.
+ */
+ if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->nc && textreadc(t, q1)=='>'){
+ rs = includename(t, r, nname);
+ r = rs.r;
+ nname = rs.nr;
+ }
+ else if(amin == q0)
+ goto Isfile;
+ else{
+ rs = dirname(t, r, nname);
+ r = rs.r;
+ nname = rs.nr;
+ }
+ e->bname = runetobyte(r, nname);
+ /* if it's already a window name, it's a file */
+ w = lookfile(r, nname);
+ if(w != nil)
+ goto Isfile;
+ /* if it's the name of a file, it's a file */
+ if(access(e->bname, 0) < 0){
+ free(e->bname);
+ e->bname = nil;
+ goto Isntfile;
+ }
+
+ Isfile:
+ e->name = r;
+ e->nname = nname;
+ e->at = t;
+ e->a0 = amin+1;
+ eval = FALSE;
+ address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
+ return TRUE;
+
+ Isntfile:
+ free(r);
+ return FALSE;
+}
+
+int
+expand(Text *t, uint q0, uint q1, Expand *e)
+{
+ memset(e, 0, sizeof *e);
+ e->agetc = tgetc;
+ /* if in selection, choose selection */
+ e->jump = TRUE;
+ if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ if(t->what == Tag)
+ e->jump = FALSE;
+ }
+
+ if(expandfile(t, q0, q1, e))
+ return TRUE;
+
+ if(q0 == q1){
+ while(q1<t->file->nc && isalnum(textreadc(t, q1)))
+ q1++;
+ while(q0>0 && isalnum(textreadc(t, q0-1)))
+ q0--;
+ }
+ e->q0 = q0;
+ e->q1 = q1;
+ return q1 > q0;
+}
+
+Window*
+lookfile(Rune *s, int n)
+{
+ int i, j, k;
+ Window *w;
+ Column *c;
+ Text *t;
+
+ /* avoid terminal slash on directories */
+ if(n>1 && s[n-1] == '/')
+ --n;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ t = &w->body;
+ k = t->file->nname;
+ if(k>1 && t->file->name[k-1] == '/')
+ k--;
+ if(runeeq(t->file->name, k, s, n)){
+ w = w->body.file->curtext->w;
+ if(w->col != nil) /* protect against race deleting w */
+ return w;
+ }
+ }
+ }
+ return nil;
+}
+
+Window*
+lookid(int id, int dump)
+{
+ int i, j;
+ Window *w;
+ Column *c;
+
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ if(dump && w->dumpid == id)
+ return w;
+ if(!dump && w->id == id)
+ return w;
+ }
+ }
+ return nil;
+}
+
+
+Window*
+openfile(Text *t, Expand *e)
+{
+ Range r;
+ Window *w, *ow;
+ int eval, i, n;
+ Rune *rp;
+ uint dummy;
+
+ if(e->nname == 0){
+ w = t->w;
+ if(w == nil)
+ return nil;
+ }else
+ w = lookfile(e->name, e->nname);
+ if(w){
+ t = &w->body;
+ if(!t->col->safe && t->maxlines==0) /* window is obscured by full-column window */
+ colgrow(t->col, t->col->w[0], 1);
+ }else{
+ ow = nil;
+ if(t)
+ ow = t->w;
+ w = makenewwindow(t);
+ t = &w->body;
+ winsetname(w, e->name, e->nname);
+ textload(t, 0, e->bname, 1);
+ t->file->mod = FALSE;
+ t->w->dirty = FALSE;
+ winsettag(t->w);
+ textsetselect(&t->w->tag, t->w->tag.file->nc, t->w->tag.file->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);
+ }
+ for(i=0; i < NINDENT; i++)
+ w->indent[i] = ow->indent[i];
+ }else
+ for(i=0; i < NINDENT; i++)
+ w->indent[i] = globalindent[i];
+ xfidlog(w, "new");
+ }
+ if(e->a1 == e->a0)
+ eval = FALSE;
+ else{
+ eval = TRUE;
+ r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->at, e->a0, e->a1, e->agetc, &eval, &dummy);
+ if(eval == FALSE)
+ e->jump = FALSE; /* don't jump if invalid address */
+ }
+ if(eval == FALSE){
+ r.q0 = t->q0;
+ r.q1 = t->q1;
+ }
+ textshow(t, r.q0, r.q1, 1);
+ winsettag(t->w);
+ seltext = t;
+ if(e->jump)
+ moveto(mousectl, addpt(frptofchar(t, t->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);
+ f = rs.r;
+ nf = rs.nr;
+ memset(&e, 0, sizeof e);
+ e.name = f;
+ e.nname = nf;
+ e.bname = runetobyte(f, nf);
+ e.jump = TRUE;
+ openfile(et, &e);
+ free(f);
+ free(e.bname);
+ arg = skipbl(a, na, &narg);
+ }
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,46 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=acme-themes
+
+OFILES=\
+ acme.$O\
+ addr.$O\
+ buff.$O\
+ cols.$O\
+ disk.$O\
+ ecmd.$O\
+ edit.$O\
+ elog.$O\
+ exec.$O\
+ file.$O\
+ fsys.$O\
+ logf.$O\
+ look.$O\
+ regx.$O\
+ rows.$O\
+ scrl.$O\
+ text.$O\
+ time.$O\
+ util.$O\
+ wind.$O\
+ xfid.$O\
+
+HFILES=dat.h\
+ edit.h\
+ fns.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a
+
+edit.$O ecmd.$O elog.$O: edit.h
+
+syms:V:
+ $CC -a acme.c > syms
+ for(i in ????.c) $CC -aa $i >> syms
--- /dev/null
+++ b/regx.c
@@ -1,0 +1,839 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+Rangeset sel;
+Rune *lastregexp;
+
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+struct Inst
+{
+ uint type; /* <= Runemax+1 ==> literal, otherwise action */
+ union {
+ int sid;
+ int subid;
+ int class;
+ Inst *other;
+ Inst *right;
+ };
+ union{
+ Inst *left;
+ Inst *next;
+ };
+};
+
+#define NPROG 1024
+Inst program[NPROG];
+Inst *progp;
+Inst *startinst; /* First inst. of program; might not be program[0] */
+Inst *bstartinst; /* same for backwards machine */
+Channel *rechan; /* chan(Inst*) */
+
+typedef struct Ilist Ilist;
+struct Ilist
+{
+ Inst *inst; /* Instruction of the thread */
+ Rangeset se;
+ uint startp; /* first char of match */
+};
+
+#define NLIST 127
+
+Ilist *tl, *nl; /* This list, next list */
+Ilist list[2][NLIST+1]; /* +1 for trailing null */
+static Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ * 0x100xx are operators, value == precedence
+ * 0x200xx are tokens, i.e. operands for operators
+ */
+enum {
+ OPERATOR = Runemask+1, /* Bitmask of all operators */
+ START = OPERATOR, /* Start, used for marker on stack */
+ RBRA, /* Right bracket, ) */
+ LBRA, /* Left bracket, ( */
+ OR, /* Alternation, | */
+ CAT, /* Concatentation, implicit operator */
+ STAR, /* Closure, * */
+ PLUS, /* a+ == aa* */
+ QUEST, /* a? == a|nothing, i.e. 0 or 1 a's */
+
+ ANY = OPERATOR<<1, /* Any character but newline, . */
+ NOP, /* No operation, internal use only */
+ BOL, /* Beginning of line, ^ */
+ EOL, /* End of line, $ */
+ CCLASS, /* Character class, [] */
+ NCCLASS, /* Negated character class, [^] */
+ END, /* Terminate: match found */
+
+ ISATOR = OPERATOR,
+ ISAND = OPERATOR<<1,
+};
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+ Inst *first;
+ Inst *last;
+};
+
+#define NSTACK 20
+Node andstack[NSTACK];
+Node *andp;
+int atorstack[NSTACK];
+int *atorp;
+int lastwasand; /* Last token was operand */
+int cursubid;
+int subidstack[NSTACK];
+int *subidp;
+int backwards;
+int nbra;
+Rune *exprp; /* pointer to next character in source expression */
+#define DCLASS 10 /* allocation increment */
+int nclass; /* number active */
+int Nclass; /* high water mark */
+Rune **class;
+int negateclass;
+
+int addinst(Ilist *l, Inst *inst, Rangeset *sep);
+void newmatch(Rangeset*);
+void bnewmatch(Rangeset*);
+void pushand(Inst*, Inst*);
+void pushator(int);
+Node *popand(int);
+int popator(void);
+void startlex(Rune*);
+int lex(void);
+void operator(int);
+void operand(int);
+void evaluntil(int);
+void optimize(Inst*);
+void bldcclass(void);
+
+void
+rxinit(void)
+{
+ rechan = chancreate(sizeof(Inst*), 0);
+ lastregexp = runemalloc(1);
+}
+
+void
+regerror(char *e)
+{
+ lastregexp[0] = 0;
+ warning(nil, "regexp: %s\n", e);
+ sendp(rechan, nil);
+ threadexits(nil);
+}
+
+Inst *
+newinst(int t)
+{
+ if(progp >= &program[NPROG])
+ regerror("expression too long");
+ progp->type = t;
+ progp->left = nil;
+ progp->right = nil;
+ return progp++;
+}
+
+void
+realcompile(void *arg)
+{
+ int token;
+ Rune *s;
+
+ threadsetname("regcomp");
+ s = arg;
+ startlex(s);
+ atorp = atorstack;
+ andp = andstack;
+ subidp = subidstack;
+ cursubid = 0;
+ lastwasand = FALSE;
+ /* Start with a low priority operator to prime parser */
+ pushator(START-1);
+ while((token=lex()) != END){
+ if((token&ISATOR) == OPERATOR)
+ operator(token);
+ else
+ operand(token);
+ }
+ /* Close with a low priority operator */
+ evaluntil(START);
+ /* Force END */
+ operand(END);
+ evaluntil(START);
+ if(nbra)
+ regerror("unmatched `('");
+ --andp; /* points to first and only operand */
+ sendp(rechan, andp->first);
+ threadexits(nil);
+}
+
+/* r is null terminated */
+int
+rxcompile(Rune *r)
+{
+ int i, nr;
+ Inst *oprogp;
+
+ nr = runestrlen(r)+1;
+ if(runeeq(lastregexp, runestrlen(lastregexp)+1, r, nr)==TRUE)
+ return TRUE;
+ lastregexp[0] = 0;
+ for(i=0; i<nclass; i++)
+ free(class[i]);
+ nclass = 0;
+ progp = program;
+ backwards = FALSE;
+ bstartinst = nil;
+ threadcreate(realcompile, r, STACK);
+ startinst = recvp(rechan);
+ if(startinst == nil)
+ return FALSE;
+ optimize(program);
+ oprogp = progp;
+ backwards = TRUE;
+ threadcreate(realcompile, r, STACK);
+ bstartinst = recvp(rechan);
+ if(bstartinst == nil)
+ return FALSE;
+ optimize(oprogp);
+ lastregexp = runerealloc(lastregexp, nr);
+ runemove(lastregexp, r, nr);
+ return TRUE;
+}
+
+void
+operand(int t)
+{
+ Inst *i;
+ if(lastwasand)
+ operator(CAT); /* catenate is implicit */
+ i = newinst(t);
+ if(t == CCLASS){
+ if(negateclass)
+ i->type = NCCLASS; /* UGH */
+ i->class = nclass-1; /* UGH */
+ }
+ pushand(i, i);
+ lastwasand = TRUE;
+}
+
+void
+operator(int t)
+{
+ if(t==RBRA && --nbra<0)
+ regerror("unmatched `)'");
+ if(t==LBRA){
+ cursubid++; /* silently ignored */
+ nbra++;
+ if(lastwasand)
+ operator(CAT);
+ }else
+ evaluntil(t);
+ if(t!=RBRA)
+ pushator(t);
+ lastwasand = FALSE;
+ if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+ lastwasand = TRUE; /* these look like operands */
+}
+
+void
+pushand(Inst *f, Inst *l)
+{
+ if(andp >= &andstack[NSTACK])
+ error("operand stack overflow");
+ andp->first = f;
+ andp->last = l;
+ andp++;
+}
+
+void
+pushator(int t)
+{
+ if(atorp >= &atorstack[NSTACK])
+ error("operator stack overflow");
+ *atorp++=t;
+ if(cursubid >= NRange)
+ *subidp++= -1;
+ else
+ *subidp++=cursubid;
+}
+
+Node *
+popand(int op)
+{
+ char buf[64];
+
+ if(andp <= &andstack[0])
+ if(op){
+ sprint(buf, "missing operand for %c", op);
+ regerror(buf);
+ }else
+ regerror("malformed regexp");
+ return --andp;
+}
+
+int
+popator()
+{
+ if(atorp <= &atorstack[0])
+ error("operator stack underflow");
+ --subidp;
+ return *--atorp;
+}
+
+void
+evaluntil(int pri)
+{
+ Node *op1, *op2, *t;
+ Inst *inst1, *inst2;
+
+ while(pri==RBRA || atorp[-1]>=pri){
+ switch(popator()){
+ case LBRA:
+ op1 = popand('(');
+ inst2 = newinst(RBRA);
+ inst2->subid = *subidp;
+ op1->last->next = inst2;
+ inst1 = newinst(LBRA);
+ inst1->subid = *subidp;
+ inst1->next = op1->first;
+ pushand(inst1, inst2);
+ return; /* must have been RBRA */
+ default:
+ error("unknown regexp operator");
+ break;
+ case OR:
+ op2 = popand('|');
+ op1 = popand('|');
+ inst2 = newinst(NOP);
+ op2->last->next = inst2;
+ op1->last->next = inst2;
+ inst1 = newinst(OR);
+ inst1->right = op1->first;
+ inst1->left = op2->first;
+ pushand(inst1, inst2);
+ break;
+ case CAT:
+ op2 = popand(0);
+ op1 = popand(0);
+ if(backwards && op2->first->type!=END){
+ t = op1;
+ op1 = op2;
+ op2 = t;
+ }
+ op1->last->next = op2->first;
+ pushand(op1->first, op2->last);
+ break;
+ case STAR:
+ op2 = popand('*');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(inst1, inst1);
+ break;
+ case PLUS:
+ op2 = popand('+');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(op2->first, inst1);
+ break;
+ case QUEST:
+ op2 = popand('?');
+ inst1 = newinst(OR);
+ inst2 = newinst(NOP);
+ inst1->left = inst2;
+ inst1->right = op2->first;
+ op2->last->next = inst2;
+ pushand(inst1, inst2);
+ break;
+ }
+ }
+}
+
+
+void
+optimize(Inst *start)
+{
+ Inst *inst, *target;
+
+ for(inst=start; inst->type!=END; inst++){
+ target = inst->next;
+ while(target->type == NOP)
+ target = target->next;
+ inst->next = target;
+ }
+}
+
+void
+startlex(Rune *s)
+{
+ exprp = s;
+ nbra = 0;
+}
+
+
+int
+lex(void){
+ int c;
+
+ c = *exprp++;
+ switch(c){
+ case '\\':
+ if(*exprp)
+ if((c= *exprp++)=='n')
+ c='\n';
+ break;
+ case 0:
+ c = END;
+ --exprp; /* In case we come here again */
+ break;
+ case '*':
+ c = STAR;
+ break;
+ case '?':
+ c = QUEST;
+ break;
+ case '+':
+ c = PLUS;
+ break;
+ case '|':
+ c = OR;
+ break;
+ case '.':
+ c = ANY;
+ break;
+ case '(':
+ c = LBRA;
+ break;
+ case ')':
+ c = RBRA;
+ break;
+ case '^':
+ c = BOL;
+ break;
+ case '$':
+ c = EOL;
+ break;
+ case '[':
+ c = CCLASS;
+ bldcclass();
+ break;
+ }
+ return c;
+}
+
+int
+nextrec(void)
+{
+ if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
+ regerror("malformed `[]'");
+ if(exprp[0] == '\\'){
+ exprp++;
+ if(*exprp=='n'){
+ exprp++;
+ return '\n';
+ }
+ return *exprp++|(Runemask+1);
+ }
+ return *exprp++;
+}
+
+void
+bldcclass(void)
+{
+ int c1, c2, n, na;
+ Rune *classp;
+
+ classp = runemalloc(DCLASS);
+ n = 0;
+ na = DCLASS;
+ /* we have already seen the '[' */
+ if(*exprp == '^'){
+ classp[n++] = '\n'; /* don't match newline in negate case */
+ negateclass = TRUE;
+ exprp++;
+ }else
+ negateclass = FALSE;
+ while((c1 = nextrec()) != ']'){
+ if(c1 == '-'){
+ Error:
+ free(classp);
+ regerror("malformed `[]'");
+ }
+ if(n+4 >= na){ /* 3 runes plus NUL */
+ na += DCLASS;
+ classp = runerealloc(classp, na);
+ }
+ if(*exprp == '-'){
+ exprp++; /* eat '-' */
+ if((c2 = nextrec()) == ']')
+ goto Error;
+ classp[n+0] = Runemax;
+ classp[n+1] = c1 & Runemask;
+ classp[n+2] = c2 & Runemask;
+ n += 3;
+ }else
+ classp[n++] = c1 & Runemask;
+ }
+ classp[n] = 0;
+ if(nclass == Nclass){
+ Nclass += DCLASS;
+ class = realloc(class, Nclass*sizeof(Rune*));
+ }
+ class[nclass++] = classp;
+}
+
+int
+classmatch(int classno, int c, int negate)
+{
+ Rune *p;
+
+ p = class[classno];
+ while(*p){
+ if(*p == Runemax){
+ if(p[1]<=c && c<=p[2])
+ return !negate;
+ p += 3;
+ }else if(*p++ == c)
+ return !negate;
+ }
+ return negate;
+}
+
+/*
+ * Note optimization in addinst:
+ * *l must be pending when addinst called; if *l has been looked
+ * at already, the optimization is a bug.
+ */
+int
+addinst(Ilist *l, Inst *inst, Rangeset *sep)
+{
+ Ilist *p;
+
+ for(p = l; p->inst; p++){
+ if(p->inst==inst){
+ if((sep)->r[0].q0 < p->se.r[0].q0)
+ p->se= *sep; /* this would be bug */
+ return 0; /* It's already there */
+ }
+ }
+ p->inst = inst;
+ p->se= *sep;
+ (p+1)->inst = nil;
+ return 1;
+}
+
+int
+rxnull(void)
+{
+ return startinst==nil || bstartinst==nil;
+}
+
+/* either t!=nil or r!=nil, and we match the string in the appropriate place */
+int
+rxexecute(Text *t, Rune *r, uint startp, uint eof, Rangeset *rp)
+{
+ int flag;
+ Inst *inst;
+ Ilist *tlp;
+ uint p;
+ int nnl, ntl;
+ int nc, c;
+ int wrapped;
+ int startchar;
+
+ flag = 0;
+ p = startp;
+ startchar = 0;
+ wrapped = 0;
+ nnl = 0;
+ if(startinst->type<OPERATOR)
+ startchar = startinst->type;
+ list[0][0].inst = list[1][0].inst = nil;
+ sel.r[0].q0 = -1;
+ if(t != nil)
+ nc = t->file->nc;
+ else
+ nc = runestrlen(r);
+ /* Execute machine once for each character */
+ for(;;p++){
+ doloop:
+ if(p>=eof || p>=nc){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to beginning */
+ if(sel.r[0].q0>=0 || eof!=Infinity)
+ goto Return;
+ list[0][0].inst = list[1][0].inst = nil;
+ p = 0;
+ goto doloop;
+ default:
+ goto Return;
+ }
+ c = 0;
+ }else{
+ if(((wrapped && p>=startp) || sel.r[0].q0>0) && nnl==0)
+ break;
+ if(t != nil)
+ c = textreadc(t, p);
+ else
+ c = r[p];
+ }
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = nil;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.r[0].q0<0 && (!wrapped || p<startp || startp==eof)){
+ /* Add first instruction to this list */
+ sempty.r[0].q0 = p;
+ if(addinst(tl, startinst, &sempty))
+ if(++ntl >= NLIST){
+ Overflow:
+ warning(nil, "regexp list overflow\n");
+ sel.r[0].q0 = -1;
+ goto Return;
+ }
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type==c){
+ Addinst:
+ if(addinst(nl, inst->next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.r[inst->subid].q0 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid>=0)
+ tlp->se.r[inst->subid].q1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c!='\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(p==0 || (t!=nil && textreadc(t, p-1)=='\n') || (r!=nil && r[p-1]=='\n')){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(c == '\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>=0 && classmatch(inst->class, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>=0 && classmatch(inst->class, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(addinst(tlp, inst->right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.r[0].q1 = p;
+ newmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ *rp = sel;
+ return sel.r[0].q0 >= 0;
+}
+
+void
+newmatch(Rangeset *sp)
+{
+ if(sel.r[0].q0<0 || sp->r[0].q0<sel.r[0].q0 ||
+ (sp->r[0].q0==sel.r[0].q0 && sp->r[0].q1>sel.r[0].q1))
+ sel = *sp;
+}
+
+int
+rxbexecute(Text *t, uint startp, Rangeset *rp)
+{
+ int flag;
+ Inst *inst;
+ Ilist *tlp;
+ int p;
+ int nnl, ntl;
+ int c;
+ int wrapped;
+ int startchar;
+
+ flag = 0;
+ nnl = 0;
+ wrapped = 0;
+ p = startp;
+ startchar = 0;
+ if(bstartinst->type<OPERATOR)
+ startchar = bstartinst->type;
+ list[0][0].inst = list[1][0].inst = nil;
+ sel.r[0].q0= -1;
+ /* Execute machine once for each character, including terminal NUL */
+ for(;;--p){
+ doloop:
+ if(p <= 0){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to end */
+ if(sel.r[0].q0>=0)
+ goto Return;
+ list[0][0].inst = list[1][0].inst = nil;
+ p = t->file->nc;
+ goto doloop;
+ case 3:
+ default:
+ goto Return;
+ }
+ c = 0;
+ }else{
+ if(((wrapped && p<=startp) || sel.r[0].q0>0) && nnl==0)
+ break;
+ c = textreadc(t, p-1);
+ }
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = nil;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.r[0].q0<0 && (!wrapped || p>startp)){
+ /* Add first instruction to this list */
+ /* the minus is so the optimizations in addinst work */
+ sempty.r[0].q0 = -p;
+ if(addinst(tl, bstartinst, &sempty))
+ if(++ntl >= NLIST){
+ Overflow:
+ warning(nil, "regexp list overflow\n");
+ sel.r[0].q0 = -1;
+ goto Return;
+ }
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type == c){
+ Addinst:
+ if(addinst(nl, inst->next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.r[inst->subid].q0 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid >= 0)
+ tlp->se.r[inst->subid].q1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c != '\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(c=='\n' || p==0){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(p<t->file->nc && textreadc(t, p)=='\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>0 && classmatch(inst->class, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>0 && classmatch(inst->class, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(addinst(tl, inst->right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.r[0].q0 = -tlp->se.r[0].q0; /* minus sign */
+ tlp->se.r[0].q1 = p;
+ bnewmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ *rp = sel;
+ return sel.r[0].q0 >= 0;
+}
+
+void
+bnewmatch(Rangeset *sp)
+{
+ int i;
+
+ if(sel.r[0].q0<0 || sp->r[0].q0>sel.r[0].q1 || (sp->r[0].q0==sel.r[0].q1 && sp->r[0].q1<sel.r[0].q0))
+ for(i = 0; i<NRange; i++){ /* note the reversal; q0<=q1 */
+ sel.r[i].q0 = sp->r[i].q1;
+ sel.r[i].q1 = sp->r[i].q0;
+ }
+}
--- /dev/null
+++ b/rows.c
@@ -1,0 +1,736 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <bio.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+rowinit(Row *row, Rectangle r)
+{
+ Rectangle r1;
+ Text *t;
+
+ draw(screen, r, display->white, nil, ZP);
+ row->r = r;
+ row->col = nil;
+ row->ncol = 0;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ t = &row->tag;
+ textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
+ t->what = Rowtag;
+ t->row = row;
+ t->w = nil;
+ t->col = nil;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ textinsert(t, 0, L"Newcol Kill Putall Dump Exit ", 29, TRUE);
+ textsetselect(t, t->file->nc, t->file->nc);
+}
+
+Column*
+rowadd(Row *row, Column *c, int x)
+{
+ Rectangle r, r1;
+ Column *d;
+ int i;
+
+ d = nil;
+ r = row->r;
+ r.min.y = row->tag.r.max.y+Border;
+ if(x<r.min.x && row->ncol>0){ /*steal 40% of last column by default */
+ d = row->col[row->ncol-1];
+ x = d->r.min.x + 3*Dx(d->r)/5;
+ }
+ /* look for column we'll land on */
+ for(i=0; i<row->ncol; i++){
+ d = row->col[i];
+ if(x < d->r.max.x)
+ break;
+ }
+ if(row->ncol > 0){
+ if(i < row->ncol)
+ i++; /* new column will go after d */
+ r = d->r;
+ if(Dx(r) < 100)
+ return nil;
+ draw(screen, r, display->white, nil, ZP);
+ r1 = r;
+ r1.max.x = min(x, r.max.x-50);
+ if(Dx(r1) < 50)
+ r1.max.x = r1.min.x+50;
+ colresize(d, r1);
+ r1.min.x = r1.max.x;
+ r1.max.x = r1.min.x+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.x = r1.max.x;
+ }
+ if(c == nil){
+ c = emalloc(sizeof(Column));
+ colinit(c, r);
+ incref(&reffont);
+ }else
+ colresize(c, r);
+ c->row = row;
+ c->tag.row = row;
+ row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
+ memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
+ row->col[i] = c;
+ row->ncol++;
+ clearmouse();
+ return c;
+}
+
+void
+rowresize(Row *row, Rectangle r)
+{
+ int i, dx, odx;
+ Rectangle r1, r2;
+ Column *c;
+
+ dx = Dx(r);
+ odx = Dx(row->r);
+ row->r = r;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textresize(&row->tag, r1);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ r1 = r;
+ r1.max.x = r1.min.x;
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ r1.min.x = r1.max.x;
+ if(i == row->ncol-1)
+ r1.max.x = r.max.x;
+ else
+ r1.max.x = r1.min.x+Dx(c->r)*dx/odx;
+ if(i > 0){
+ r2 = r1;
+ r2.max.x = r2.min.x+Border;
+ draw(screen, r2, display->black, nil, ZP);
+ r1.min.x = r2.max.x;
+ }
+ colresize(c, r1);
+ }
+}
+
+void
+rowdragcol(Row *row, Column *c, int)
+{
+ Rectangle r;
+ int i, b, x;
+ Point p, op;
+ Column *d;
+
+ clearmouse();
+ setcursor(mousectl, &boxcursor);
+ b = mouse->buttons;
+ op = mouse->xy;
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ setcursor(mousectl, nil);
+ if(mouse->buttons){
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return;
+ }
+
+ for(i=0; i<row->ncol; i++)
+ if(row->col[i] == c)
+ goto Found;
+ error("can't find column");
+
+ Found:
+ p = mouse->xy;
+ if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
+ return;
+ if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
+ /* shuffle */
+ x = c->r.min.x;
+ rowclose(row, c, FALSE);
+ if(rowadd(row, c, p.x) == nil) /* whoops! */
+ if(rowadd(row, c, x) == nil) /* WHOOPS! */
+ if(rowadd(row, c, -1)==nil){ /* shit! */
+ rowclose(row, c, TRUE);
+ return;
+ }
+ colmousebut(c);
+ return;
+ }
+ if(i == 0)
+ return;
+ d = row->col[i-1];
+ if(p.x < d->r.min.x+80+Scrollwid)
+ p.x = d->r.min.x+80+Scrollwid;
+ if(p.x > c->r.max.x-80-Scrollwid)
+ p.x = c->r.max.x-80-Scrollwid;
+ r = d->r;
+ r.max.x = c->r.max.x;
+ draw(screen, r, display->white, nil, ZP);
+ r.max.x = p.x;
+ colresize(d, r);
+ r = c->r;
+ r.min.x = p.x;
+ r.max.x = r.min.x;
+ r.max.x += Border;
+ draw(screen, r, display->black, nil, ZP);
+ r.min.x = r.max.x;
+ r.max.x = c->r.max.x;
+ colresize(c, r);
+ colmousebut(c);
+}
+
+void
+rowclose(Row *row, Column *c, int dofree)
+{
+ Rectangle r;
+ int i;
+
+ for(i=0; i<row->ncol; i++)
+ if(row->col[i] == c)
+ goto Found;
+ error("can't find column");
+ Found:
+ r = c->r;
+ if(dofree)
+ colcloseall(c);
+ memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
+ row->ncol--;
+ row->col = realloc(row->col, row->ncol*sizeof(Column*));
+ if(row->ncol == 0){
+ draw(screen, r, display->white, nil, ZP);
+ return;
+ }
+ if(i == row->ncol){ /* extend last column right */
+ c = row->col[i-1];
+ r.min.x = c->r.min.x;
+ r.max.x = row->r.max.x;
+ }else{ /* extend next window left */
+ c = row->col[i];
+ r.max.x = c->r.max.x;
+ }
+ draw(screen, r, display->white, nil, ZP);
+ colresize(c, r);
+}
+
+Column*
+rowwhichcol(Row *row, Point p)
+{
+ int i;
+ Column *c;
+
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ if(ptinrect(p, c->r))
+ return c;
+ }
+ return nil;
+}
+
+Text*
+rowwhich(Row *row, Point p)
+{
+ Column *c;
+
+ if(ptinrect(p, row->tag.all))
+ return &row->tag;
+ c = rowwhichcol(row, p);
+ if(c)
+ return colwhich(c, p);
+ return nil;
+}
+
+Text*
+rowtype(Row *row, Rune r, Point p)
+{
+ Window *w;
+ Text *t;
+
+ clearmouse();
+ qlock(row);
+ if(bartflag)
+ t = barttext;
+ else
+ t = rowwhich(row, p);
+ if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
+ w = t->w;
+ if(w == nil)
+ texttype(t, r);
+ else{
+ winlock(w, 'K');
+ wintype(w, t, r);
+ winunlock(w);
+ }
+ }
+ qunlock(row);
+ return t;
+}
+
+int
+rowclean(Row *row)
+{
+ int clean;
+ int i;
+
+ clean = TRUE;
+ for(i=0; i<row->ncol; i++)
+ clean &= colclean(row->col[i]);
+ return clean;
+}
+
+void
+rowdump(Row *row, char *file)
+{
+ int i, j, fd, m, n, start, dumped;
+ uint q0, q1;
+ Biobuf *b;
+ char *buf, *a, *fontname;
+ Rune *r;
+ Column *c;
+ Window *w, *w1;
+ Text *t;
+
+ if(row->ncol == 0)
+ return;
+ buf = fbufalloc();
+ if(file == nil){
+ if(home == nil){
+ warning(nil, "can't find file for dump: $home not defined\n");
+ goto Rescue;
+ }
+ sprint(buf, "%s/acme.dump", home);
+ file = buf;
+ }
+ fd = create(file, OWRITE, 0600);
+ if(fd < 0){
+ warning(nil, "can't open %s: %r\n", file);
+ goto Rescue;
+ }
+ b = emalloc(sizeof(Biobuf));
+ Binit(b, fd, OWRITE);
+ r = fbufalloc();
+ Bprint(b, "%s\n", wdir);
+ Bprint(b, "%s\n", fontnames[0]);
+ Bprint(b, "%s\n", fontnames[1]);
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ Bprint(b, "%11d", 100*(c->r.min.x-row->r.min.x)/Dx(row->r));
+ if(i == row->ncol-1)
+ Bputc(b, '\n');
+ else
+ Bputc(b, ' ');
+ }
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ for(j=0; j<c->nw; j++)
+ c->w[j]->body.file->dumpid = 0;
+ }
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ for(j=0; j<c->nw; j++){
+ w = c->w[j];
+ wincommit(w, &w->tag);
+ t = &w->body;
+ /* windows owned by others get special treatment */
+ if(w->nopen[QWevent] > 0)
+ if(w->dumpstr == nil)
+ continue;
+ /* zeroxes of external windows are tossed */
+ if(t->file->ntext > 1)
+ for(n=0; n<t->file->ntext; n++){
+ w1 = t->file->text[n]->w;
+ if(w == w1)
+ continue;
+ if(w1->nopen[QWevent])
+ goto Continue2;
+ }
+ fontname = "";
+ if(t->reffont->f != font)
+ fontname = t->reffont->f->name;
+ if(t->file->nname)
+ a = runetobyte(t->file->name, t->file->nname);
+ else
+ a = emalloc(1);
+ if(t->file->dumpid){
+ dumped = FALSE;
+ Bprint(b, "x%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
+ w->body.q0, w->body.q1,
+ 100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+ fontname);
+ }else if(w->dumpstr){
+ dumped = FALSE;
+ Bprint(b, "e%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
+ 0, 0,
+ 100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+ fontname);
+ }else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
+ dumped = FALSE;
+ t->file->dumpid = w->id;
+ Bprint(b, "f%11d %11d %11d %11d %11d %s\n", i, w->id,
+ w->body.q0, w->body.q1,
+ 100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+ fontname);
+ }else{
+ dumped = TRUE;
+ t->file->dumpid = w->id;
+ Bprint(b, "F%11d %11d %11d %11d %11d %11d %s\n", i, j,
+ w->body.q0, w->body.q1,
+ 100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+ w->body.file->nc, fontname);
+ }
+ free(a);
+ winctlprint(w, buf, 0);
+ Bwrite(b, buf, strlen(buf));
+ m = min(RBUFSIZE, w->tag.file->nc);
+ bufread(w->tag.file, 0, r, m);
+ n = 0;
+ while(n<m) {
+ start = n;
+ while(n<m && r[n]!='\n')
+ n++;
+ Bprint(b, "%.*S", n-start, r+start);
+ if(n<m) {
+ Bputc(b, 0xff); // \n in tag becomes 0xff byte (invalid UTF)
+ n++;
+ }
+ }
+ Bprint(b, "\n");
+ if(dumped){
+ q0 = 0;
+ q1 = t->file->nc;
+ while(q0 < q1){
+ n = q1 - q0;
+ if(n > BUFSIZE/UTFmax)
+ n = BUFSIZE/UTFmax;
+ bufread(t->file, q0, r, n);
+ Bprint(b, "%.*S", n, r);
+ q0 += n;
+ }
+ }
+ if(w->dumpstr){
+ if(w->dumpdir)
+ Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
+ else
+ Bprint(b, "\n%s\n", w->dumpstr);
+ }
+ Continue2:;
+ }
+ }
+ Bterm(b);
+ close(fd);
+ free(b);
+ fbuffree(r);
+
+ Rescue:
+ fbuffree(buf);
+}
+
+static
+char*
+rdline(Biobuf *b, int *linep)
+{
+ char *l;
+
+ l = Brdline(b, '\n');
+ if(l)
+ (*linep)++;
+ return l;
+}
+
+/*
+ * Get font names from load file so we don't load fonts we won't use
+ */
+void
+rowloadfonts(char *file)
+{
+ int i;
+ Biobuf *b;
+ char *l;
+
+ b = Bopen(file, OREAD);
+ if(b == nil)
+ return;
+ /* current directory */
+ l = Brdline(b, '\n');
+ if(l == nil)
+ goto Return;
+ /* global fonts */
+ for(i=0; i<2; i++){
+ l = Brdline(b, '\n');
+ if(l == nil)
+ goto Return;
+ l[Blinelen(b)-1] = 0;
+ if(*l && strcmp(l, fontnames[i])!=0){
+ free(fontnames[i]);
+ fontnames[i] = estrdup(l);
+ }
+ }
+ Return:
+ Bterm(b);
+}
+
+int
+rowload(Row *row, char *file, int initing)
+{
+ int i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd;
+ Biobuf *b, *bout;
+ char *buf, *l, *t, *fontname;
+ Rune *r, rune, *fontr;
+ Column *c, *c1, *c2;
+ uint q0, q1;
+ Rectangle r1, r2;
+ Window *w;
+
+ buf = fbufalloc();
+ if(file == nil){
+ if(home == nil){
+ warning(nil, "can't find file for load: $home not defined\n");
+ goto Rescue1;
+ }
+ sprint(buf, "%s/acme.dump", home);
+ file = buf;
+ }
+ b = Bopen(file, OREAD);
+ if(b == nil){
+ warning(nil, "can't open load file %s: %r\n", file);
+ goto Rescue1;
+ }
+ /* current directory */
+ line = 0;
+ l = rdline(b, &line);
+ if(l == nil)
+ goto Rescue2;
+ l[Blinelen(b)-1] = 0;
+ if(chdir(l) < 0){
+ warning(nil, "can't chdir %s\n", l);
+ goto Rescue2;
+ }
+ /* global fonts */
+ for(i=0; i<2; i++){
+ l = rdline(b, &line);
+ if(l == nil)
+ goto Rescue2;
+ l[Blinelen(b)-1] = 0;
+ if(*l && strcmp(l, fontnames[i])!=0)
+ rfget(i, TRUE, i==0 && initing, l);
+ }
+ if(initing && row->ncol==0)
+ rowinit(row, screen->clipr);
+ l = rdline(b, &line);
+ if(l == nil)
+ goto Rescue2;
+ j = Blinelen(b)/12;
+ if(j<=0 || j>10)
+ goto Rescue2;
+ for(i=0; i<j; i++){
+ percent = atoi(l+i*12);
+ if(percent<0 || percent>=100)
+ goto Rescue2;
+ x = row->r.min.x+percent*Dx(row->r)/100;
+ if(i < row->ncol){
+ if(i == 0)
+ continue;
+ c1 = row->col[i-1];
+ c2 = row->col[i];
+ r1 = c1->r;
+ r2 = c2->r;
+ r1.max.x = x;
+ r2.min.x = x+Border;
+ if(Dx(r1) < 50 || Dx(r2) < 50)
+ continue;
+ draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP);
+ colresize(c1, r1);
+ colresize(c2, r2);
+ r2.min.x = x;
+ r2.max.x = x+Border;
+ draw(screen, r2, display->black, nil, ZP);
+ }
+ if(i >= row->ncol)
+ rowadd(row, nil, x);
+ }
+ for(;;){
+ l = rdline(b, &line);
+ if(l == nil)
+ break;
+ dumpid = 0;
+ switch(l[0]){
+ case 'e':
+ if(Blinelen(b) < 1+5*12+1)
+ goto Rescue2;
+ l = rdline(b, &line); /* ctl line; ignored */
+ if(l == nil)
+ goto Rescue2;
+ l = rdline(b, &line); /* directory */
+ if(l == nil)
+ goto Rescue2;
+ l[Blinelen(b)-1] = 0;
+ if(*l == '\0'){
+ if(home == nil)
+ r = bytetorune("./", &nr);
+ else{
+ t = emalloc(strlen(home)+1+1);
+ sprint(t, "%s/", home);
+ r = bytetorune(t, &nr);
+ free(t);
+ }
+ }else
+ r = bytetorune(l, &nr);
+ l = rdline(b, &line); /* command */
+ if(l == nil)
+ goto Rescue2;
+ t = emalloc(Blinelen(b)+1);
+ memmove(t, l, Blinelen(b));
+ run(nil, t, r, nr, TRUE, nil, nil, FALSE);
+ /* r is freed in run() */
+ continue;
+ case 'f':
+ if(Blinelen(b) < 1+5*12+1)
+ goto Rescue2;
+ fontname = l+1+5*12;
+ ndumped = -1;
+ break;
+ case 'F':
+ if(Blinelen(b) < 1+6*12+1)
+ goto Rescue2;
+ fontname = l+1+6*12;
+ ndumped = atoi(l+1+5*12+1);
+ break;
+ case 'x':
+ if(Blinelen(b) < 1+5*12+1)
+ goto Rescue2;
+ fontname = l+1+5*12;
+ ndumped = -1;
+ dumpid = atoi(l+1+1*12);
+ break;
+ default:
+ goto Rescue2;
+ }
+ l[Blinelen(b)-1] = 0;
+ fontr = nil;
+ nfontr = 0;
+ if(*fontname)
+ fontr = bytetorune(fontname, &nfontr);
+ i = atoi(l+1+0*12);
+ j = atoi(l+1+1*12);
+ q0 = atoi(l+1+2*12);
+ q1 = atoi(l+1+3*12);
+ percent = atoi(l+1+4*12);
+ if(i<0 || i>10)
+ goto Rescue2;
+ if(i > row->ncol)
+ i = row->ncol;
+ c = row->col[i];
+ y = c->r.min.y+(percent*Dy(c->r))/100;
+ if(y<c->r.min.y || y>=c->r.max.y)
+ y = -1;
+ if(dumpid == 0)
+ w = coladd(c, nil, nil, y);
+ else
+ w = coladd(c, nil, lookid(dumpid, TRUE), y);
+ if(w == nil)
+ continue;
+ w->dumpid = j;
+ l = rdline(b, &line);
+ if(l == nil)
+ goto Rescue2;
+ l[Blinelen(b)-1] = 0;
+ /* convert 0xff in multiline tag back to \n */
+ for(i=0; l[i]!=0; i++)
+ if((uchar)l[i] == 0xff)
+ l[i] = '\n';
+ r = bytetorune(l+5*12, &nr);
+ ns = -1;
+ for(n=0; n<nr; n++){
+ if(r[n] == '/')
+ ns = n;
+ if(r[n] == ' ')
+ break;
+ }
+ if(dumpid == 0)
+ winsetname(w, r, n);
+ for(; n<nr; n++)
+ if(r[n] == '|')
+ break;
+ wincleartag(w);
+ textinsert(&w->tag, w->tag.file->nc, r+n+1, nr-(n+1), TRUE);
+ if(ndumped >= 0){
+ /* simplest thing is to put it in a file and load that */
+ sprint(buf, "/tmp/d%d.%.4sacme", getpid(), user);
+ fd = create(buf, OWRITE|ORCLOSE, 0600);
+ if(fd < 0){
+ free(r);
+ warning(nil, "can't create temp file: %r\n");
+ goto Rescue2;
+ }
+ bout = emalloc(sizeof(Biobuf));
+ Binit(bout, fd, OWRITE);
+ for(n=0; n<ndumped; n++){
+ rune = Bgetrune(b);
+ if(rune == '\n')
+ line++;
+ if(rune == (Rune)Beof){
+ free(r);
+ Bterm(bout);
+ free(bout);
+ close(fd);
+ goto Rescue2;
+ }
+ Bputrune(bout, rune);
+ }
+ Bterm(bout);
+ free(bout);
+ textload(&w->body, 0, buf, 1);
+ close(fd);
+ w->body.file->mod = TRUE;
+ for(n=0; n<w->body.file->ntext; n++)
+ w->body.file->text[n]->w->dirty = TRUE;
+ winsettag(w);
+ }else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
+ get(&w->body, nil, nil, FALSE, XXX, nil, 0);
+ if(fontr){
+ fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
+ free(fontr);
+ }
+ free(r);
+ if(q0>w->body.file->nc || q1>w->body.file->nc || q0>q1)
+ q0 = q1 = 0;
+ textshow(&w->body, q0, q1, 1);
+ w->maxlines = min(w->body.nlines, max(w->maxlines, w->body.maxlines));
+ xfidlog(w, "new");
+ }
+ Bterm(b);
+ fbuffree(buf);
+ return TRUE;
+
+Rescue2:
+ warning(nil, "bad load file %s:%d\n", file, line);
+ Bterm(b);
+Rescue1:
+ fbuffree(buf);
+ return FALSE;
+}
+
+void
+allwindows(void (*f)(Window*, void*), void *arg)
+{
+ int i, j;
+ Column *c;
+
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c->nw; j++)
+ (*f)(c->w[j], arg);
+ }
+}
--- /dev/null
+++ b/scrl.c
@@ -1,0 +1,153 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Image *scrtmp;
+
+static
+Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ tot>>=10;
+ p0>>=10;
+ p1>>=10;
+ }
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+scrlresize(void)
+{
+ freeimage(scrtmp);
+ scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill);
+ if(scrtmp == nil)
+ error("scroll alloc");
+}
+
+void
+textscrdraw(Text *t)
+{
+ Rectangle r, r1, r2;
+ Image *b;
+
+ if(t->w==nil || t!=&t->w->body)
+ return;
+ if(scrtmp == nil)
+ scrlresize();
+ r = t->scrollr;
+ b = scrtmp;
+ r1 = r;
+ r1.min.x = 0;
+ r1.max.x = Dx(r);
+ r2 = scrpos(r1, t->org, t->org+t->nchars, t->file->nc);
+ if(!eqrect(r2, t->lastsr)){
+ t->lastsr = r2;
+ draw(b, r1, t->cols[BORD], nil, ZP);
+ draw(b, r2, t->cols[BACK], nil, ZP);
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, t->cols[BORD], nil, ZP);
+ draw(t->b, r, b, nil, Pt(0, r1.min.y));
+/*flushimage(display, 1);/*BUG?*/
+ }
+}
+
+void
+scrsleep(uint dt)
+{
+ Timer *timer;
+ static Alt alts[3];
+
+ timer = timerstart(dt);
+ alts[0].c = timer->c;
+ alts[0].v = nil;
+ alts[0].op = CHANRCV;
+ alts[1].c = mousectl->c;
+ alts[1].v = &mousectl->Mouse;
+ alts[1].op = CHANRCV;
+ alts[2].op = CHANEND;
+ for(;;)
+ switch(alt(alts)){
+ case 0:
+ timerstop(timer);
+ return;
+ case 1:
+ timercancel(timer);
+ return;
+ }
+}
+
+void
+textscroll(Text *t, int but)
+{
+ uint p0, oldp0;
+ Rectangle s;
+ int y, my, h, first;
+
+ s = insetrect(t->scrollr, 1);
+ h = s.max.y-s.min.y;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ flushimage(display, 1);
+ my = mouse->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 2){
+ y = my;
+ p0 = (vlong)t->file->nc*(y-s.min.y)/h;
+ if(p0 >= t->q1)
+ p0 = textbacknl(t, p0, 2);
+ if(oldp0 != p0)
+ textsetorigin(t, p0, FALSE);
+ oldp0 = p0;
+ readmouse(mousectl);
+ continue;
+ }
+ if(but == 1)
+ p0 = textbacknl(t, t->org, (my-s.min.y)/t->font->height);
+ else
+ p0 = t->org+frcharofpt(t, Pt(s.max.x, my));
+ if(oldp0 != p0)
+ textsetorigin(t, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ flushimage(display, 1);
+ sleep(200);
+ nbrecv(mousectl->c, &mousectl->Mouse);
+ first = FALSE;
+ }
+ scrsleep(80);
+ }while(mouse->buttons & (1<<(but-1)));
+ while(mouse->buttons)
+ readmouse(mousectl);
+}
--- /dev/null
+++ b/text.c
@@ -1,0 +1,1461 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+
+enum{
+ TABDIR = 3 /* width of tabs in directory windows */
+};
+
+void
+textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
+{
+ t->file = f;
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ t->eq0 = ~0;
+ t->ncache = 0;
+ t->reffont = rf;
+ t->tabstop = maxtab;
+ memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
+ textredraw(t, r, rf->f, screen, -1);
+}
+
+void
+textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
+{
+ int maxt;
+ Rectangle rr;
+
+ frinit(t, r, f, b, t->Frame.cols);
+ rr = t->r;
+ rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */
+ draw(t->b, rr, t->cols[BACK], nil, ZP);
+ /* use no wider than 3-space tabs in a directory */
+ maxt = maxtab;
+ if(t->what == Body){
+ if(t->w->isdir)
+ maxt = min(TABDIR, maxtab);
+ else
+ maxt = t->tabstop;
+ }
+ t->maxtab = maxt*stringwidth(f, "0");
+ if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
+ if(t->maxlines > 0){
+ textreset(t);
+ textcolumnate(t, t->w->dlp, t->w->ndl);
+ textshow(t, 0, 0, 1);
+ }
+ }else{
+ textfill(t);
+ textsetselect(t, t->q0, t->q1);
+ }
+}
+
+int
+textresize(Text *t, Rectangle r)
+{
+ int odx;
+
+ if(Dy(r) > 0)
+ r.max.y -= Dy(r)%t->font->height;
+ else
+ r.max.y = r.min.y;
+ odx = Dx(t->all);
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ frclear(t, 0);
+ textredraw(t, r, t->font, t->b, odx);
+ return r.max.y;
+}
+
+void
+textclose(Text *t)
+{
+ free(t->cache);
+ frclear(t, 1);
+ filedeltext(t->file, t);
+ t->file = nil;
+ rfclose(t->reffont);
+ if(argtext == t)
+ argtext = nil;
+ if(typetext == t)
+ typetext = nil;
+ if(seltext == t)
+ seltext = nil;
+ if(mousetext == t)
+ mousetext = nil;
+ if(barttext == t)
+ barttext = nil;
+}
+
+int
+dircmp(void *a, void *b)
+{
+ Dirlist *da, *db;
+ int i, n;
+
+ da = *(Dirlist**)a;
+ db = *(Dirlist**)b;
+ n = min(da->nr, db->nr);
+ i = memcmp(da->r, db->r, n*sizeof(Rune));
+ if(i)
+ return i;
+ return da->nr - db->nr;
+}
+
+void
+textcolumnate(Text *t, Dirlist **dlp, int ndl)
+{
+ int i, j, w, colw, mint, maxt, ncol, nrow;
+ Dirlist *dl;
+ uint q1;
+
+ if(t->file->ntext > 1)
+ return;
+ mint = stringwidth(t->font, "0");
+ /* go for narrower tabs if set more than 3 wide */
+ t->maxtab = min(maxtab, TABDIR)*mint;
+ maxt = t->maxtab;
+ colw = 0;
+ for(i=0; i<ndl; i++){
+ dl = dlp[i];
+ w = dl->wid;
+ if(maxt-w%maxt < mint || w%maxt==0)
+ w += mint;
+ if(w % maxt)
+ w += maxt-(w%maxt);
+ if(w > colw)
+ colw = w;
+ }
+ if(colw == 0)
+ ncol = 1;
+ else
+ ncol = max(1, Dx(t->r)/colw);
+ nrow = (ndl+ncol-1)/ncol;
+
+ q1 = 0;
+ for(i=0; i<nrow; i++){
+ for(j=i; j<ndl; j+=nrow){
+ dl = dlp[j];
+ fileinsert(t->file, q1, dl->r, dl->nr);
+ q1 += dl->nr;
+ if(j+nrow >= ndl)
+ break;
+ w = dl->wid;
+ if(maxt-w%maxt < mint){
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += mint;
+ }
+ do{
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += maxt-(w%maxt);
+ }while(w < colw);
+ }
+ fileinsert(t->file, q1, L"\n", 1);
+ q1++;
+ }
+}
+
+uint
+textload(Text *t, uint q0, char *file, int setqid)
+{
+ Rune *rp;
+ Dirlist *dl, **dlp;
+ int fd, i, j, n, ndl, nulls;
+ uint q, q1;
+ Dir *d, *dbuf;
+ char *tmp;
+ Text *u;
+
+ if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body)
+ error("text.load");
+ if(t->w->isdir && t->file->nname==0){
+ warning(nil, "empty directory name\n");
+ return 0;
+ }
+ fd = open(file, OREAD);
+ if(fd < 0){
+ warning(nil, "can't open %s: %r\n", file);
+ return 0;
+ }
+ d = dirfstat(fd);
+ if(d == nil){
+ warning(nil, "can't fstat %s: %r\n", file);
+ goto Rescue;
+ }
+ nulls = FALSE;
+ if(d->qid.type & QTDIR){
+ /* this is checked in get() but it's possible the file changed underfoot */
+ if(t->file->ntext > 1){
+ warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
+ goto Rescue;
+ }
+ t->w->isdir = TRUE;
+ t->w->filemenu = FALSE;
+ if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
+ rp = runemalloc(t->file->nname+1);
+ runemove(rp, t->file->name, t->file->nname);
+ rp[t->file->nname] = '/';
+ winsetname(t->w, rp, t->file->nname+1);
+ free(rp);
+ }
+ dlp = nil;
+ ndl = 0;
+ dbuf = nil;
+ while((n=dirread(fd, &dbuf)) > 0){
+ for(i=0; i<n; i++){
+ dl = emalloc(sizeof(Dirlist));
+ j = strlen(dbuf[i].name);
+ tmp = emalloc(j+1+1);
+ memmove(tmp, dbuf[i].name, j);
+ if(dbuf[i].qid.type & QTDIR)
+ tmp[j++] = '/';
+ tmp[j] = '\0';
+ dl->r = bytetorune(tmp, &dl->nr);
+ dl->wid = stringwidth(t->font, tmp);
+ free(tmp);
+ ndl++;
+ dlp = realloc(dlp, ndl*sizeof(Dirlist*));
+ dlp[ndl-1] = dl;
+ }
+ free(dbuf);
+ }
+ qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
+ t->w->dlp = dlp;
+ t->w->ndl = ndl;
+ textcolumnate(t, dlp, ndl);
+ q1 = t->file->nc;
+ }else{
+ t->w->isdir = FALSE;
+ t->w->filemenu = TRUE;
+ q1 = q0 + fileload(t->file, q0, fd, &nulls);
+ }
+ if(setqid){
+ t->file->dev = d->dev;
+ t->file->mtime = d->mtime;
+ t->file->qidpath = d->qid.path;
+ }
+ close(fd);
+ rp = fbufalloc();
+ for(q=q0; q<q1; q+=n){
+ n = q1-q;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(t->file, q, rp, n);
+ if(q < t->org)
+ t->org += n;
+ else if(q <= t->org+t->nchars)
+ frinsert(t, rp, rp+n, q-t->org);
+ if(t->lastlinefull)
+ break;
+ }
+ fbuffree(rp);
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */
+ u->org = 0;
+ textresize(u, u->all);
+ textbacknl(u, u->org, 0); /* go to beginning of line */
+ }
+ textsetselect(u, q0, q0);
+ }
+ if(nulls)
+ warning(nil, "%s: NUL bytes elided\n", file);
+ free(d);
+ return q1-q0;
+
+ Rescue:
+ close(fd);
+ return 0;
+}
+
+uint
+textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
+{
+ Rune *bp, *tp, *up;
+ int i, initial;
+
+ if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
+ Err:
+ textinsert(t, q0, r, n, tofile);
+ *nrp = n;
+ return q0;
+ }
+ bp = r;
+ for(i=0; i<n; i++)
+ if(*bp++ == '\b'){
+ --bp;
+ initial = 0;
+ tp = runemalloc(n);
+ runemove(tp, r, i);
+ up = tp+i;
+ for(; i<n; i++){
+ *up = *bp++;
+ if(*up == '\b')
+ if(up == tp)
+ initial++;
+ else
+ --up;
+ else
+ up++;
+ }
+ if(initial){
+ if(initial > q0)
+ initial = q0;
+ q0 -= initial;
+ textdelete(t, q0, q0+initial, tofile);
+ }
+ n = up-tp;
+ textinsert(t, q0, tp, n, tofile);
+ free(tp);
+ *nrp = n;
+ return q0;
+ }
+ goto Err;
+}
+
+void
+textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
+{
+ int c, i;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.insert");
+ if(n == 0)
+ return;
+ if(tofile){
+ fileinsert(t->file, q0, r, n);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textinsert(u, q0, r, n, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+
+ }
+ if(q0 < t->q1)
+ t->q1 += n;
+ if(q0 < t->q0)
+ t->q0 += n;
+ if(q0 < t->org)
+ t->org += n;
+ else if(q0 <= t->org+t->nchars)
+ frinsert(t, r, r+n, q0-t->org);
+ if(t->w){
+ c = 'i';
+ if(t->what == Body)
+ c = 'I';
+ if(n <= EVENTSIZE)
+ winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
+ else
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
+ }
+}
+
+void
+typecommit(Text *t)
+{
+ if(t->w != nil)
+ wincommit(t->w, t);
+ else
+ textcommit(t, TRUE);
+}
+
+void
+textfill(Text *t)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(t->lastlinefull || t->nofill)
+ return;
+ if(t->ncache > 0)
+ typecommit(t);
+ rp = fbufalloc();
+ do{
+ n = t->file->nc-(t->org+t->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ bufread(t->file, t->org+t->nchars, rp, n);
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = t->maxlines-t->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(t, rp, rp+i, t->nchars);
+ }while(t->lastlinefull == FALSE);
+ fbuffree(rp);
+}
+
+void
+textdelete(Text *t, uint q0, uint q1, int tofile)
+{
+ uint n, p0, p1;
+ int i, c;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.delete");
+ n = q1-q0;
+ if(n == 0)
+ return;
+ if(tofile){
+ filedelete(t->file, q0, q1);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textdelete(u, q0, q1, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+ }
+ if(q0 < t->q0)
+ t->q0 -= min(n, t->q0-q0);
+ if(q0 < t->q1)
+ t->q1 -= min(n, t->q1-q0);
+ if(q1 <= t->org)
+ t->org -= n;
+ else if(q0 < t->org+t->nchars){
+ p1 = q1 - t->org;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(q0 < t->org){
+ t->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - t->org;
+ frdelete(t, p0, p1);
+ textfill(t);
+ }
+ if(t->w){
+ c = 'd';
+ if(t->what == Body)
+ c = 'D';
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
+ }
+}
+
+void
+textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
+{
+ *p0 = min(q0, t->file->nc);
+ *p1 = min(q1, t->file->nc);
+}
+
+Rune
+textreadc(Text *t, uint q)
+{
+ Rune r;
+
+ if(t->cq0<=q && q<t->cq0+t->ncache)
+ r = t->cache[q-t->cq0];
+ else
+ bufread(t->file, q, &r, 1);
+ return r;
+}
+
+static int
+spacesindentbswidth(Text *t)
+{
+ uint q, col;
+ Rune r;
+
+ col = textbswidth(t, 0x15);
+ q = t->q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r != ' ')
+ break;
+ q--;
+ if(--col % t->tabstop == 0)
+ break;
+ }
+ if(t->q0 == q)
+ return 1;
+ return t->q0-q;
+}
+
+int
+textbswidth(Text *t, Rune c)
+{
+ uint q, eq;
+ Rune r;
+ int skipping;
+
+ /* there is known to be at least one character to erase */
+ if(c == 0x08){ /* ^H: erase character */
+ if(t->what == Body && t->w->indent[SPACESINDENT])
+ return spacesindentbswidth(t);
+ return 1;
+ }
+ q = t->q0;
+ skipping = TRUE;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == t->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ if(c == 0x17){
+ eq = isalnum(r);
+ if(eq && skipping) /* found one; stop skipping */
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return t->q0-q;
+}
+
+int
+textfilewidth(Text *t, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r <= ' ')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+Rune*
+textcomplete(Text *t)
+{
+ int i, nstr, npath;
+ uint q;
+ Rune tmp[200];
+ Rune *str, *path;
+ Rune *rp;
+ Completion *c;
+ char *s, *dirs;
+ Runestr dir;
+
+ /* control-f: filename completion; works back to white space or / */
+ if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */
+ return nil;
+ nstr = textfilewidth(t, t->q0, TRUE);
+ str = runemalloc(nstr);
+ npath = textfilewidth(t, t->q0-nstr, FALSE);
+ path = runemalloc(npath);
+
+ c = nil;
+ rp = nil;
+ dirs = nil;
+
+ q = t->q0-nstr;
+ for(i=0; i<nstr; i++)
+ str[i] = textreadc(t, q++);
+ q = t->q0-nstr-npath;
+ for(i=0; i<npath; i++)
+ path[i] = textreadc(t, q++);
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = (Runestr){path, npath};
+ else{
+ dir = dirname(t, nil, 0);
+ if(dir.nr + 1 + npath > nelem(tmp)){
+ free(dir.r);
+ goto Return;
+ }
+ if(dir.nr == 0){
+ dir.nr = 1;
+ dir.r = runestrdup(L".");
+ }
+ runemove(tmp, dir.r, dir.nr);
+ tmp[dir.nr] = '/';
+ runemove(tmp+dir.nr+1, path, npath);
+ free(dir.r);
+ dir.r = tmp;
+ dir.nr += 1+npath;
+ dir = cleanrname(dir);
+ }
+
+ s = smprint("%.*S", nstr, str);
+ dirs = smprint("%.*S", dir.nr, dir.r);
+ c = complete(dirs, s);
+ free(s);
+ if(c == nil){
+ warning(nil, "error attempting completion: %r\n");
+ goto Return;
+ }
+
+ if(!c->advance){
+ warning(nil, "%.*S%s%.*S*%s\n",
+ dir.nr, dir.r,
+ dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
+ nstr, str,
+ c->nmatch? "" : ": no matches in:");
+ for(i=0; i<c->nfile; i++)
+ warning(nil, " %s\n", c->filename[i]);
+ }
+
+ if(c->advance)
+ rp = runesmprint("%s", c->string);
+ else
+ rp = nil;
+ Return:
+ freecompletion(c);
+ free(dirs);
+ free(str);
+ free(path);
+ return rp;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+ uint q0, q1;
+ int nnb, nb, n, i;
+ int nr;
+ Rune rr;
+ Rune *rp;
+ Text *u;
+
+ nr = 1;
+ rp = &r;
+ switch(r){
+ case Kleft:
+ typecommit(t);
+ if(t->q0 > 0)
+ textshow(t, t->q0-1, t->q0-1, TRUE);
+ return;
+ case Kright:
+ typecommit(t);
+ if(t->q1 < t->file->nc)
+ textshow(t, t->q1+1, t->q1+1, TRUE);
+ return;
+ case Kdown:
+ n = t->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(t->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*t->maxlines/3;
+ case_Down:
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Kup:
+ n = t->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(t->maxlines);
+ goto case_Up;
+ case Kpgup:
+ n = 2*t->maxlines/3;
+ case_Up:
+ q0 = textbacknl(t, t->org, n);
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Khome:
+ typecommit(t);
+ textshow(t, 0, 0, FALSE);
+ return;
+ case Kend:
+ typecommit(t);
+ textshow(t, t->file->nc, t->file->nc, FALSE);
+ return;
+ case 0x01: /* ^A: beginning of line */
+ typecommit(t);
+ /* go to where ^U would erase, if not already at BOL */
+ nnb = 0;
+ if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
+ nnb = textbswidth(t, 0x15);
+ textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
+ return;
+ case 0x05: /* ^E: end of line */
+ typecommit(t);
+ q0 = t->q0;
+ while(q0<t->file->nc && textreadc(t, q0)!='\n')
+ q0++;
+ textshow(t, q0, q0, TRUE);
+ return;
+ }
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ if(t->q1 > t->q0){
+ if(t->ncache != 0)
+ error("text.type");
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ t->eq0 = ~0;
+ }
+ textshow(t, t->q0, t->q0, 1);
+ switch(r){
+ case 0x06:
+ case Kins:
+ rp = textcomplete(t);
+ if(rp == nil)
+ return;
+ nr = runestrlen(rp);
+ break; /* fall through to normal insertion case */
+ case 0x1B:
+ if(t->eq0 != ~0)
+ textsetselect(t, t->eq0, t->q0);
+ if(t->ncache > 0)
+ typecommit(t);
+ return;
+ case 0x08: /* ^H: erase character */
+ case 0x15: /* ^U: erase line */
+ case 0x17: /* ^W: erase word */
+ if(t->q0 == 0) /* nothing to erase */
+ return;
+ nnb = textbswidth(t, r);
+ q1 = t->q0;
+ q0 = q1-nnb;
+ /* if selection is at beginning of window, avoid deleting invisible text */
+ if(q0 < t->org){
+ q0 = t->org;
+ nnb = q1-q0;
+ }
+ if(nnb <= 0)
+ return;
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ u->nofill = TRUE;
+ nb = nnb;
+ n = u->ncache;
+ if(n > 0){
+ if(q1 != u->cq0+n)
+ error("text.type backspace");
+ if(n > nb)
+ n = nb;
+ u->ncache -= n;
+ textdelete(u, q1-n, q1, FALSE);
+ nb -= n;
+ }
+ if(u->eq0==q1 || u->eq0==~0)
+ u->eq0 = q0;
+ if(nb && u==t)
+ textdelete(u, q0, q0+nb, TRUE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ else
+ textsetselect(t, q0, q0);
+ u->nofill = FALSE;
+ }
+ for(i=0; i<t->file->ntext; i++)
+ textfill(t->file->text[i]);
+ return;
+ case '\t':
+ if(t->what == Body && t->w->indent[SPACESINDENT]){
+ nnb = textbswidth(t, 0x15);
+ if(nnb == 1 && textreadc(t, t->q0-1) == '\n')
+ nnb = 0;
+ nnb = t->tabstop - nnb % t->tabstop;
+ rp = runemalloc(nnb);
+ for(nr = 0; nr < nnb; nr++)
+ rp[nr] = ' ';
+ }
+ break;
+ case '\n':
+ if(t->what == Body && t->w->indent[AUTOINDENT]){
+ /* find beginning of previous line using backspace code */
+ nnb = textbswidth(t, 0x15); /* ^U case */
+ rp = runemalloc(nnb + 1);
+ nr = 0;
+ rp[nr++] = r;
+ for(i=0; i<nnb; i++){
+ rr = textreadc(t, t->q0-nnb+i);
+ if(rr != ' ' && rr != '\t')
+ break;
+ rp[nr++] = rr;
+ }
+ }
+ break; /* fall through to normal code */
+ }
+ /* otherwise ordinary character; just insert, typically in caches of all texts */
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u->eq0 == ~0)
+ u->eq0 = t->q0;
+ if(u->ncache == 0)
+ u->cq0 = t->q0;
+ else if(t->q0 != u->cq0+u->ncache)
+ error("text.type cq1");
+ textinsert(u, t->q0, rp, nr, FALSE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ if(u->ncache+nr > u->ncachealloc){
+ u->ncachealloc += 10 + nr;
+ u->cache = runerealloc(u->cache, u->ncachealloc);
+ }
+ runemove(u->cache+u->ncache, rp, nr);
+ u->ncache += nr;
+ }
+ if(rp != &r)
+ free(rp);
+ textsetselect(t, t->q0+nr, t->q0+nr);
+ if(r=='\n' && t->w!=nil)
+ wincommit(t->w, t);
+}
+
+void
+textcommit(Text *t, int tofile)
+{
+ if(t->ncache == 0)
+ return;
+ if(tofile)
+ fileinsert(t->file, t->cq0, t->cache, t->ncache);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ t->ncache = 0;
+}
+
+static Text *clicktext;
+static uint clickmsec;
+static int clickcount;
+static Point clickpt;
+static Text *selecttext;
+static uint selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ error("frameselect not right frame");
+ textframescroll(selecttext, dl);
+}
+
+void
+textframescroll(Text *t, int dl)
+{
+ uint q0;
+
+ if(dl == 0){
+ scrsleep(100);
+ return;
+ }
+ if(dl < 0){
+ q0 = textbacknl(t, t->org, -dl);
+ if(selectq > t->org+t->p0)
+ textsetselect(t, t->org+t->p0, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p0);
+ }else{
+ if(t->org+t->nchars == t->file->nc)
+ return;
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
+ if(selectq > t->org+t->p1)
+ textsetselect(t, t->org+t->p1, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p1);
+ }
+ textsetorigin(t, q0, TRUE);
+ flushimage(display, 1);
+}
+
+
+void
+textselect(Text *t)
+{
+ uint q0, q1;
+ int b, x, y, dx, dy;
+ int state;
+
+ selecttext = t;
+ /*
+ * To have double-clicking and chording, we double-click
+ * immediately if it might make sense.
+ */
+ b = mouse->buttons;
+ q0 = t->q0;
+ q1 = t->q1;
+ dx = abs(clickpt.x - mouse->xy.x);
+ dy = abs(clickpt.y - mouse->xy.y);
+ clickpt = mouse->xy;
+ selectq = t->org+frcharofpt(t, mouse->xy);
+ clickcount++;
+ if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3)
+ clickcount = 0;
+ if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ x = mouse->xy.x;
+ y = mouse->xy.y;
+ /* stay here until something interesting happens */
+ while(1){
+ readmouse(mousectl);
+ dx = abs(mouse->xy.x - x);
+ dy = abs(mouse->xy.y - y);
+ if(mouse->buttons != b || dx >= 3 || dy >= 3)
+ break;
+ clickcount++;
+ clickmsec = mouse->msec;
+ }
+ mouse->xy.x = x; /* in case we're calling frselect */
+ mouse->xy.y = y;
+ q0 = t->q0; /* may have changed */
+ q1 = t->q1;
+ selectq = t->org+frcharofpt(t, mouse->xy);;
+ }
+ if(mouse->buttons == b && clickcount == 0){
+ t->Frame.scroll = framescroll;
+ frselect(t, mousectl);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > t->file->nc)
+ selectq = t->org + t->p0;
+ t->Frame.scroll = nil;
+ if(selectq < t->org)
+ q0 = selectq;
+ else
+ q0 = t->org + t->p0;
+ if(selectq > t->org+t->nchars)
+ q1 = selectq;
+ else
+ q1 = t->org+t->p1;
+ }
+ if(q0 == q1){
+ if(q0==t->q0 && mouse->msec-clickmsec<500)
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ else
+ clicktext = t;
+ clickmsec = mouse->msec;
+ }else
+ clicktext = nil;
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ state = 0; /* undo when possible; +1 for cut, -1 for paste */
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if((b&1) && (b&6)){
+ if(state==0 && t->what==Body){
+ seq++;
+ filemark(t->w->body.file);
+ }
+ if(b & 2){
+ if(state==-1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q0);
+ state = 0;
+ }else if(state != 1){
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ state = 1;
+ }
+ }else{
+ if(state==1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q1);
+ state = 0;
+ }else if(state != -1){
+ paste(t, t, nil, TRUE, FALSE, nil, 0);
+ state = -1;
+ }
+ }
+ textscrdraw(t);
+ clearmouse();
+ }
+ flushimage(display, 1);
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ if(mouse->msec-clickmsec >= 500)
+ clicktext = nil;
+ }
+}
+
+void
+textshow(Text *t, uint q0, uint q1, int doselect)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ if(t->what != Body){
+ if(doselect)
+ textsetselect(t, q0, q1);
+ return;
+ }
+ if(t->w!=nil && t->maxlines==0)
+ colgrow(t->col, t->w, 1);
+ if(doselect)
+ textsetselect(t, q0, q1);
+ qe = t->org+t->nchars;
+ if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
+ textscrdraw(t);
+ else{
+ if(t->w->nopen[QWevent] > 0)
+ nl = 3*t->maxlines/4;
+ else
+ nl = t->maxlines/4;
+ q = textbacknl(t, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>t->org && q<t->org))
+ textsetorigin(t, q, TRUE);
+ while(q0 > t->org+t->nchars)
+ textsetorigin(t, t->org+1, FALSE);
+ }
+}
+
+static
+int
+region(int a, int b)
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+void
+selrestore(Frame *f, Point pt0, uint p0, uint p1)
+{
+ if(p1<=f->p0 || p0>=f->p1){
+ /* no overlap */
+ frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
+ return;
+ }
+ if(p0>=f->p0 && p1<=f->p1){
+ /* entirely inside */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+ return;
+ }
+
+ /* they now are known to overlap */
+
+ /* before selection */
+ if(p0 < f->p0){
+ frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
+ p0 = f->p0;
+ pt0 = frptofchar(f, p0);
+ }
+ /* after selection */
+ if(p1 > f->p1){
+ frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
+ p1 = f->p1;
+ }
+ /* inside selection */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+}
+
+void
+textsetselect(Text *t, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
+ t->q0 = q0;
+ t->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-t->org;
+ p1 = q1-t->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t->nchars)
+ p0 = t->nchars;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(p0==t->p0 && p1==t->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
+ frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < t->p0){
+ /* extend selection backwards */
+ frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
+ }else if(p0 > t->p0){
+ /* trim first part of selection */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
+ }
+ if(p1 > t->p1){
+ /* extend selection forwards */
+ frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
+ }else if(p1 < t->p1){
+ /* trim last part of selection */
+ frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
+ }
+
+ Return:
+ t->p0 = p0;
+ t->p1 = p1;
+}
+
+/*
+ * Release the button in less than DELAY ms and it's considered a null selection
+ * if the mouse hardly moved, regardless of whether it crossed a char boundary.
+ */
+enum {
+ DELAY = 2,
+ MINMOVE = 4,
+};
+
+uint
+xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
+{
+ uint p0, p1, q, tmp;
+ ulong msec;
+ Point mp, pt0, pt1, qt;
+ int reg, b;
+
+ mp = mc->xy;
+ b = mc->buttons;
+ msec = mc->msec;
+
+ /* remove tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 0);
+ p0 = p1 = frcharofpt(f, mp);
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = 0;
+ frtick(f, pt0, 1);
+ do{
+ q = frcharofpt(f, mc->xy);
+ if(p1 != q){
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ if(reg != region(q, p0)){ /* crossed starting point; reset */
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel0(f, pt0, p0, p1, col, display->white);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel0(f, pt1, p1, q, col, display->white);
+
+ else if(q < p1)
+ selrestore(f, qt, q, p1);
+ }else if(reg < 0){
+ if(q > p1)
+ selrestore(f, pt1, p1, q);
+ else
+ frdrawsel0(f, qt, q, p1, col, display->white);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ if(p0 == p1)
+ frtick(f, pt0, 1);
+ flushimage(f->display, 1);
+ readmouse(mc);
+ }while(mc->buttons == b);
+ if(mc->msec-msec < DELAY && p0!=p1
+ && abs(mp.x-mc->xy.x)<MINMOVE
+ && abs(mp.y-mc->xy.y)<MINMOVE) {
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ }
+ if(p1 < p0){
+ tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+ pt0 = frptofchar(f, p0);
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ selrestore(f, pt0, p0, p1);
+ /* restore tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 1);
+ flushimage(f->display, 1);
+ *p1p = p1;
+ return p0;
+}
+
+int
+textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
+{
+ uint p0, p1;
+ int buts;
+
+ p0 = xselect(t, mousectl, high, &p1);
+ buts = mousectl->buttons;
+ if((buts & mask) == 0){
+ *q0 = p0+t->org;
+ *q1 = p1+t->org;
+ }
+
+ while(mousectl->buttons)
+ readmouse(mousectl);
+ return buts;
+}
+
+int
+textselect2(Text *t, uint *q0, uint *q1, Text **tp)
+{
+ int buts;
+
+ *tp = nil;
+ buts = textselect23(t, q0, q1, but2col, 4);
+ if(buts & 4)
+ return 0;
+ if(buts & 1){ /* pick up argument */
+ *tp = argtext;
+ return 1;
+ }
+ return 1;
+}
+
+int
+textselect3(Text *t, uint *q0, uint *q1)
+{
+ int h;
+
+ h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
+ return h;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static
+Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static
+Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+void
+textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = mp;
+ *q1 = mp;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = textreadc(t, q-1);
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(textclickmatch(t, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == t->file->nc)
+ c = '\n';
+ else
+ c = textreadc(t, q);
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(textclickmatch(t, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<t->file->nc && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(textreadc(t, *q0-1), mode))
+ (*q0)--;
+}
+
+int
+textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == t->file->nc)
+ break;
+ c = textreadc(t, *q);
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = textreadc(t, *q);
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+uint
+textbacknl(Text *t, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && textreadc(t, p-1)!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(textreadc(t, p-1)=='\n')
+ break;
+ }
+ return p;
+}
+
+void
+textsetorigin(Text *t, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact && textreadc(t, org-1) != '\n'){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<t->file->nc; i++){
+ if(textreadc(t, org) == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-t->org;
+ fixup = 0;
+ if(a>=0 && a<t->nchars){
+ frdelete(t, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }
+ else if(a<0 && -a<t->nchars){
+ n = t->org - org;
+ r = runemalloc(n);
+ bufread(t->file, org, r, n);
+ frinsert(t, r, r+n, 0);
+ free(r);
+ }else
+ frdelete(t, 0, t->nchars);
+ t->org = org;
+ textfill(t);
+ textscrdraw(t);
+ textsetselect(t, t->q0, t->q1);
+ if(fixup && t->p1 > t->p0)
+ frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
+}
+
+void
+textreset(Text *t)
+{
+ t->file->seq = 0;
+ t->eq0 = ~0;
+ /* do t->delete(0, t->nc, TRUE) without building backup stuff */
+ textsetselect(t, t->org, t->org);
+ frdelete(t, 0, t->nchars);
+ t->org = 0;
+ t->q0 = 0;
+ t->q1 = 0;
+ filereset(t->file);
+ bufreset(t->file);
+}
--- /dev/null
+++ b/time.c
@@ -1,0 +1,120 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Channel* ctimer; /* chan(Timer*)[100] */
+static Timer *timer;
+
+static
+uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+ t->next = timer;
+ timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+ t->cancel = TRUE;
+}
+
+static
+void
+timerproc(void*)
+{
+ int i, nt, na, dt, del;
+ Timer **t, *x;
+ uint old, new;
+
+ threadsetname("timerproc");
+ rfork(RFFDG);
+ t = nil;
+ na = 0;
+ nt = 0;
+ old = msec();
+ for(;;){
+ sleep(1); /* will sleep minimum incr */
+ new = msec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) /* timer wrapped; go around, losing a tick */
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x->dt -= dt;
+ del = FALSE;
+ if(x->cancel){
+ timerstop(x);
+ del = TRUE;
+ }else if(x->dt <= 0){
+ /*
+ * avoid possible deadlock if client is
+ * now sending on ctimer
+ */
+ if(nbsendul(x->c, 0) > 0)
+ del = TRUE;
+ }
+ if(del){
+ memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+ --nt;
+ --i;
+ }
+ }
+ if(nt == 0){
+ x = recvp(ctimer);
+ gotit:
+ if(nt == na){
+ na += 10;
+ t = realloc(t, na*sizeof(Timer*));
+ if(t == nil)
+ error("timer realloc failed");
+ }
+ t[nt++] = x;
+ old = msec();
+ }
+ if(nbrecv(ctimer, &x) > 0)
+ goto gotit;
+ }
+}
+
+void
+timerinit(void)
+{
+ ctimer = chancreate(sizeof(Timer*), 100);
+ proccreate(timerproc, nil, STACK);
+}
+
+Timer*
+timerstart(int dt)
+{
+ Timer *t;
+
+ t = timer;
+ if(t)
+ timer = timer->next;
+ else{
+ t = emalloc(sizeof(Timer));
+ t->c = chancreate(sizeof(int), 0);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,485 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Point prevmouse;
+static Window *mousew;
+
+void
+cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
+{
+ uchar *q;
+ Rune *s;
+ int j, w;
+
+ /*
+ * Always guaranteed that n bytes may be interpreted
+ * without worrying about partial runes. This may mean
+ * reading up to UTFmax-1 more bytes than n; the caller
+ * knows this. If n is a firm limit, the caller should
+ * set p[n] = 0.
+ */
+ q = (uchar*)p;
+ s = r;
+ for(j=0; j<n; j+=w){
+ if(*q < Runeself){
+ w = 1;
+ *s = *q++;
+ }else{
+ w = chartorune(s, (char*)q);
+ q += w;
+ }
+ if(*s)
+ s++;
+ else if(nulls)
+ *nulls = TRUE;
+ }
+ *nb = (char*)q-p;
+ *nr = s-r;
+}
+
+void
+error(char *s)
+{
+ fprint(2, "acme: %s: %r\n", s);
+ remove(acmeerrorfile);
+ abort();
+}
+
+Window*
+errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
+{
+ Window *w;
+ Rune *r;
+ int i, n;
+
+ r = runemalloc(ndir+8);
+ if(n = ndir){ /* assign = */
+ runemove(r, dir, ndir);
+ r[n++] = L'/';
+ }
+ runemove(r+n, L"+Errors", 7);
+ n += 7;
+ w = lookfile(r, n);
+ if(w == nil){
+ if(row.ncol == 0)
+ if(rowadd(&row, nil, -1) == nil)
+ error("can't create column to make error window");
+ w = coladd(row.col[row.ncol-1], nil, nil, -1);
+ w->filemenu = FALSE;
+ winsetname(w, r, n);
+ xfidlog(w, "new");
+ }
+ free(r);
+ for(i=nincl; --i>=0; ){
+ n = runestrlen(incl[i]);
+ r = runemalloc(n);
+ runemove(r, incl[i], n);
+ winaddincl(w, r, n);
+ }
+ for(i=0; i<NINDENT; i++)
+ w->indent[i] = globalindent[i];
+ return w;
+}
+
+/* make new window, if necessary; return with it locked */
+Window*
+errorwin(Mntdir *md, int owner)
+{
+ Window *w;
+
+ for(;;){
+ if(md == nil)
+ w = errorwin1(nil, 0, nil, 0);
+ else
+ w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
+ winlock(w, owner);
+ if(w->col != nil)
+ break;
+ /* window was deleted too fast */
+ winunlock(w);
+ }
+ return w;
+}
+
+/*
+ * Incoming window should be locked.
+ * It will be unlocked and returned window
+ * will be locked in its place.
+ */
+Window*
+errorwinforwin(Window *w)
+{
+ int i, n, nincl, owner;
+ Rune **incl;
+ Runestr dir;
+ Text *t;
+
+ t = &w->body;
+ dir = dirname(t, nil, 0);
+ if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
+ free(dir.r);
+ dir.r = nil;
+ dir.nr = 0;
+ }
+ incl = nil;
+ nincl = w->nincl;
+ if(nincl > 0){
+ incl = emalloc(nincl*sizeof(Rune*));
+ for(i=0; i<nincl; i++){
+ n = runestrlen(w->incl[i]);
+ incl[i] = runemalloc(n+1);
+ runemove(incl[i], w->incl[i], n);
+ }
+ }
+ owner = w->owner;
+ winunlock(w);
+ for(;;){
+ w = errorwin1(dir.r, dir.nr, incl, nincl);
+ winlock(w, owner);
+ if(w->col != nil)
+ break;
+ /* window deleted too fast */
+ winunlock(w);
+ }
+ return w;
+}
+
+typedef struct Warning Warning;
+
+struct Warning{
+ Mntdir *md;
+ Buffer buf;
+ Warning *next;
+};
+
+static Warning *warnings;
+
+static
+void
+addwarningtext(Mntdir *md, Rune *r, int nr)
+{
+ Warning *warn;
+
+ for(warn = warnings; warn; warn=warn->next){
+ if(warn->md == md){
+ bufinsert(&warn->buf, warn->buf.nc, r, nr);
+ return;
+ }
+ }
+ warn = emalloc(sizeof(Warning));
+ warn->next = warnings;
+ warn->md = md;
+ if(md)
+ fsysincid(md);
+ warnings = warn;
+ bufinsert(&warn->buf, 0, r, nr);
+ nbsendp(cwarn, 0);
+}
+
+/* called while row is locked */
+void
+flushwarnings(void)
+{
+ Warning *warn, *next;
+ Window *w;
+ Text *t;
+ int owner, nr, q0, n;
+ Rune *r;
+
+ for(warn=warnings; warn; warn=next) {
+ w = errorwin(warn->md, 'E');
+ t = &w->body;
+ owner = w->owner;
+ if(owner == 0)
+ w->owner = 'E';
+ wincommit(w, t);
+ /*
+ * Most commands don't generate much output. For instance,
+ * Edit ,>cat goes through /dev/cons and is already in blocks
+ * because of the i/o system, but a few can. Edit ,p will
+ * put the entire result into a single hunk. So it's worth doing
+ * this in blocks (and putting the text in a buffer in the first
+ * place), to avoid a big memory footprint.
+ */
+ r = fbufalloc();
+ q0 = t->file->nc;
+ for(n = 0; n < warn->buf.nc; n += nr){
+ nr = warn->buf.nc - n;
+ if(nr > RBUFSIZE)
+ nr = RBUFSIZE;
+ bufread(&warn->buf, n, r, nr);
+ textbsinsert(t, t->file->nc, r, nr, TRUE, &nr);
+ }
+ textshow(t, q0, t->file->nc, 1);
+ free(r);
+ winsettag(t->w);
+ textscrdraw(t);
+ w->owner = owner;
+ w->dirty = FALSE;
+ winunlock(w);
+ bufclose(&warn->buf);
+ next = warn->next;
+ if(warn->md)
+ fsysdelid(warn->md);
+ free(warn);
+ }
+ warnings = nil;
+}
+
+void
+warning(Mntdir *md, char *s, ...)
+{
+ Rune *r;
+ va_list arg;
+
+ va_start(arg, s);
+ r = runevsmprint(s, arg);
+ va_end(arg);
+ if(r == nil)
+ error("runevsmprint failed");
+ addwarningtext(md, r, runestrlen(r));
+ free(r);
+}
+
+int
+runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
+{
+ if(n1 != n2)
+ return FALSE;
+ return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
+}
+
+uint
+min(uint a, uint b)
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+uint
+max(uint a, uint b)
+{
+ if(a > b)
+ return a;
+ return b;
+}
+
+char*
+runetobyte(Rune *r, int n)
+{
+ char *s;
+
+ if(r == nil)
+ return nil;
+ s = emalloc(n*UTFmax+1);
+ setmalloctag(s, getcallerpc(&r));
+ snprint(s, n*UTFmax+1, "%.*S", n, r);
+ return s;
+}
+
+Rune*
+bytetorune(char *s, int *ip)
+{
+ Rune *r;
+ int nb, nr;
+
+ nb = strlen(s);
+ r = runemalloc(nb+1);
+ cvttorunes(s, nb, r, &nb, &nr, nil);
+ r[nr] = '\0';
+ *ip = nr;
+ return r;
+}
+
+int
+isspace(Rune c)
+{
+ return c == 0 || c == ' ' || c == '\t' ||
+ c == '\n' || c == '\r' || c == '\v';
+}
+
+int
+isalnum(Rune c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ *
+ * Treat 0xA0 (non-breaking space) as a special alphanumeric
+ * character [sape]
+ */
+ if(c <= ' ')
+ return FALSE;
+ if(0x7F<=c && c<0xA0)
+ return FALSE;
+ if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+ return FALSE;
+ return TRUE;
+}
+
+int
+rgetc(void *v, uint n)
+{
+ return ((Rune*)v)[n];
+}
+
+int
+tgetc(void *a, uint n)
+{
+ Text *t;
+
+ t = a;
+ if(n >= t->file->nc)
+ return 0;
+ return textreadc(t, n);
+}
+
+Rune*
+skipbl(Rune *r, int n, int *np)
+{
+ while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
+ --n;
+ r++;
+ }
+ *np = n;
+ return r;
+}
+
+Rune*
+findbl(Rune *r, int n, int *np)
+{
+ while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
+ --n;
+ r++;
+ }
+ *np = n;
+ return r;
+}
+
+void
+savemouse(Window *w)
+{
+ prevmouse = mouse->xy;
+ mousew = w;
+}
+
+int
+restoremouse(Window *w)
+{
+ int did;
+
+ did = 0;
+ if(mousew!=nil && mousew==w) {
+ moveto(mousectl, prevmouse);
+ did = 1;
+ }
+ mousew = nil;
+ return did;
+}
+
+void
+clearmouse()
+{
+ mousew = nil;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = strdup(s);
+ if(t == nil)
+ error("strdup failed");
+ setmalloctag(t, getcallerpc(&s));
+ return t;
+}
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("malloc failed");
+ setmalloctag(p, getcallerpc(&n));
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("realloc failed");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+/*
+ * Heuristic city.
+ */
+Window*
+makenewwindow(Text *t)
+{
+ Column *c;
+ Window *w, *bigw, *emptyw;
+ Text *emptyb;
+ int i, y, el;
+
+ if(activecol)
+ c = activecol;
+ else if(seltext && seltext->col)
+ c = seltext->col;
+ else if(t && t->col)
+ c = t->col;
+ else{
+ if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
+ error("can't make column");
+ c = row.col[row.ncol-1];
+ }
+ activecol = c;
+ if(t==nil || t->w==nil || c->nw==0)
+ return coladd(c, nil, nil, -1);
+
+ /* find biggest window and biggest blank spot */
+ emptyw = c->w[0];
+ bigw = emptyw;
+ for(i=1; i<c->nw; i++){
+ w = c->w[i];
+ /* use >= to choose one near bottom of screen */
+ if(w->body.maxlines >= bigw->body.maxlines)
+ bigw = w;
+ if(w->body.maxlines-w->body.nlines >= emptyw->body.maxlines-emptyw->body.nlines)
+ emptyw = w;
+ }
+ emptyb = &emptyw->body;
+ el = emptyb->maxlines-emptyb->nlines;
+ /* if empty space is big, use it */
+ if(el>15 || (el>3 && el>(bigw->body.maxlines-1)/2))
+ y = emptyb->r.min.y+emptyb->nlines*font->height;
+ else{
+ /* if this window is in column and isn't much smaller, split it */
+ if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
+ bigw = t->w;
+ y = (bigw->r.min.y + bigw->r.max.y)/2;
+ }
+ w = coladd(c, nil, nil, y);
+ if(w->body.maxlines < 2)
+ colgrow(w->col, w, 1);
+ return w;
+}
--- /dev/null
+++ b/wind.c
@@ -1,0 +1,697 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+int winid;
+
+void
+wininit(Window *w, Window *clone, Rectangle r)
+{
+ Rectangle r1, br;
+ File *f;
+ Reffont *rf;
+ Rune *rp;
+ int nc, i;
+
+ w->tag.w = w;
+ w->taglines = 1;
+ w->tagexpand = TRUE;
+ w->body.w = w;
+ w->id = ++winid;
+ incref(w);
+ if(globalincref)
+ incref(w);
+ w->ctlfid = ~0;
+ w->utflastqid = -1;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ w->tagtop = r;
+ w->tagtop.max.y = r.min.y + font->height;
+ incref(&reffont);
+ f = fileaddtext(nil, &w->tag);
+ textinit(&w->tag, f, r1, &reffont, tagcols);
+ w->tag.what = Tag;
+ /* tag is a copy of the contents, not a tracked image */
+ if(clone){
+ textdelete(&w->tag, 0, w->tag.file->nc, TRUE);
+ nc = clone->tag.file->nc;
+ rp = runemalloc(nc);
+ bufread(clone->tag.file, 0, rp, nc);
+ textinsert(&w->tag, 0, rp, nc, TRUE);
+ free(rp);
+ filereset(w->tag.file);
+ textsetselect(&w->tag, nc, nc);
+ }
+ r1 = r;
+ r1.min.y += w->taglines*font->height + 1;
+ if(r1.max.y < r1.min.y)
+ r1.max.y = r1.min.y;
+ f = nil;
+ if(clone){
+ f = clone->body.file;
+ w->body.org = clone->body.org;
+ w->isscratch = clone->isscratch;
+ rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
+ }else
+ rf = rfget(FALSE, FALSE, FALSE, nil);
+ f = fileaddtext(f, &w->body);
+ w->body.what = Body;
+ textinit(&w->body, f, r1, rf, textcols);
+ r1.min.y -= 1;
+ r1.max.y = r1.min.y+1;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ textscrdraw(&w->body);
+ w->r = r;
+ w->r.max.y = w->body.r.max.y;
+ br.min = w->tag.scrollr.min;
+ br.max.x = br.min.x + Dx(button->r);
+ br.max.y = br.min.y + Dy(button->r);
+ draw(screen, br, button, nil, button->r.min);
+ w->filemenu = TRUE;
+ w->maxlines = w->body.maxlines;
+ for(i=0; i<NINDENT; i++)
+ w->indent[i] = globalindent[i];
+ if(clone){
+ w->dirty = clone->dirty;
+ for(i=0; i<NINDENT; i++)
+ w->indent[i] = clone->indent[i];
+ textsetselect(&w->body, clone->body.q0, clone->body.q1);
+ winsettag(w);
+ }
+}
+
+int
+tagrunepos(Window *w, Rune *s)
+{
+ int n;
+ Rune *r, *rr;
+
+ if(s == nil)
+ return -1;
+
+ n = w->tag.file->nc;
+ r = runemalloc(n+1);
+ bufread(w->tag.file, 0, r, n);
+ r[n] = L'\0';
+
+ rr = runestrstr(r, s);
+ if(rr == nil || rr == r)
+ return -1;
+ return rr - r;
+}
+
+void
+movetodel(Window *w)
+{
+ int n;
+
+ n = tagrunepos(w, delcmd);
+ free(delcmd);
+ delcmd = nil;
+ if(n < 0)
+ return;
+ moveto(mousectl, addpt(frptofchar(&w->tag, n), Pt(4, w->tag.font->height-4)));
+}
+
+/*
+ * Compute number of tag lines required
+ * to display entire tag text.
+ */
+int
+wintaglines(Window *w, Rectangle r)
+{
+ int n;
+ Rune rune;
+ Point p;
+
+ if(!w->tagexpand && !w->showdel)
+ return 1;
+ w->showdel = FALSE;
+ w->noredraw = 1;
+ textresize(&w->tag, r);
+ w->noredraw = 0;
+ w->tagsafe = FALSE;
+
+ if(!w->tagexpand) {
+ /* use just as many lines as needed to show the Del */
+ n = tagrunepos(w, delcmd);
+ if(n < 0)
+ return 1;
+ p = subpt(frptofchar(&w->tag, n), w->tag.r.min);
+ return 1 + p.y / w->tag.font->height;
+ }
+
+ /* can't use more than we have */
+ if(w->tag.nlines >= w->tag.maxlines)
+ return w->tag.maxlines;
+
+ /* if tag ends with \n, include empty line at end for typing */
+ n = w->tag.nlines;
+ if(w->tag.file->nc > 0){
+ bufread(w->tag.file, w->tag.file->nc-1, &rune, 1);
+ if(rune == '\n')
+ n++;
+ }
+ if(n == 0)
+ n = 1;
+ return n;
+}
+
+int
+winresize(Window *w, Rectangle r, int safe)
+{
+ int oy, mouseintag, mouseinbody;
+ Point p;
+ Rectangle r1;
+ int y;
+ Image *b;
+ Rectangle br;
+
+ mouseintag = ptinrect(mouse->xy, w->tag.all);
+ mouseinbody = ptinrect(mouse->xy, w->body.all);
+
+ w->tagtop = r;
+ w->tagtop.max.y = r.min.y+font->height;
+
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
+
+ if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
+ w->taglines = wintaglines(w, r);
+ r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
+ }
+ if(Dy(r1) < font->height)
+ r1.max.y = r1.min.y+font->height;
+ y = r1.max.y;
+ if(!safe || !eqrect(w->tag.r, r1)){
+ textresize(&w->tag, r1);
+ y = w->tag.r.max.y;
+ b = button;
+ if(w->body.file->mod && !w->isdir && !w->isscratch)
+ b = modbutton;
+ br.min = w->tag.scrollr.min;
+ br.max.x = br.min.x + Dx(b->r);
+ br.max.y = br.min.y + Dy(b->r);
+ draw(screen, br, b, nil, b->r.min);
+
+ w->tagsafe = TRUE;
+
+ /* If mouse is in tag, pull up as tag closes. */
+ if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){
+ p = mouse->xy;
+ p.y = w->tag.all.max.y-3;
+ moveto(mousectl, p);
+ }
+
+ /* If mouse is in body, push down as tag expands. */
+ if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){
+ p = mouse->xy;
+ p.y = w->tag.all.max.y+3;
+ moveto(mousectl, p);
+ }
+
+ }
+ if(!safe || !eqrect(w->body.r, r1)){
+ oy = y;
+ if(y+1+w->body.font->height <= r.max.y){ /* room for one line */
+ r1.min.y = y;
+ r1.max.y = y+1;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ y++;
+ r1.min.y = min(y, r.max.y);
+ r1.max.y = r.max.y;
+ }else{
+ r1.min.y = y;
+ r1.max.y = y;
+ }
+ y = textresize(&w->body, r1);
+ w->r = r;
+ w->r.max.y = y;
+ textscrdraw(&w->body);
+ w->body.all.min.y = oy;
+ }
+ w->maxlines = min(w->body.nlines, max(w->maxlines, w->body.maxlines));
+ return w->r.max.y;
+}
+
+void
+winlock1(Window *w, int owner)
+{
+ incref(w);
+ qlock(w);
+ w->owner = owner;
+}
+
+void
+winlock(Window *w, int owner)
+{
+ int i;
+ File *f;
+
+ f = w->body.file;
+ for(i=0; i<f->ntext; i++)
+ winlock1(f->text[i]->w, owner);
+}
+
+void
+winunlock(Window *w)
+{
+ int i;
+ File *f;
+
+ /*
+ * subtle: loop runs backwards to avoid tripping over
+ * winclose indirectly editing f->text and freeing f
+ * on the last iteration of the loop.
+ */
+ f = w->body.file;
+ for(i=f->ntext-1; i>=0; i--){
+ w = f->text[i]->w;
+ w->owner = 0;
+ qunlock(w);
+ winclose(w);
+ }
+}
+
+void
+winmousebut(Window *w)
+{
+ moveto(mousectl, addpt(w->tag.scrollr.min, divpt(Pt(Dx(w->tag.scrollr), font->height), 2)));
+}
+
+void
+windirfree(Window *w)
+{
+ int i;
+ Dirlist *dl;
+
+ if(w->isdir){
+ for(i=0; i<w->ndl; i++){
+ dl = w->dlp[i];
+ free(dl->r);
+ free(dl);
+ }
+ free(w->dlp);
+ }
+ w->dlp = nil;
+ w->ndl = 0;
+}
+
+void
+winclose(Window *w)
+{
+ int i;
+
+ if(decref(w) == 0){
+ xfidlog(w, "del");
+ windirfree(w);
+ textclose(&w->tag);
+ textclose(&w->body);
+ if(activewin == w)
+ activewin = nil;
+ for(i=0; i<w->nincl; i++)
+ free(w->incl[i]);
+ free(w->incl);
+ free(w->events);
+ free(w);
+ }
+}
+
+void
+windelete(Window *w)
+{
+ Xfid *x;
+
+ x = w->eventx;
+ if(x){
+ w->nevents = 0;
+ free(w->events);
+ w->events = nil;
+ w->eventx = nil;
+ sendp(x->c, nil); /* wake him up */
+ }
+}
+
+void
+winundo(Window *w, int isundo)
+{
+ Text *body;
+ int i;
+ File *f;
+ Window *v;
+
+ w->utflastqid = -1;
+ body = &w->body;
+ fileundo(body->file, isundo, &body->q0, &body->q1);
+ textshow(body, body->q0, body->q1, 1);
+ f = body->file;
+ for(i=0; i<f->ntext; i++){
+ v = f->text[i]->w;
+ v->dirty = (f->seq != v->putseq);
+ if(v != w){
+ v->body.q0 = v->body.p0+v->body.org;
+ v->body.q1 = v->body.p1+v->body.org;
+ }
+ }
+ winsettag(w);
+}
+
+void
+winsetname(Window *w, Rune *name, int n)
+{
+ Text *t;
+ Window *v;
+ int i;
+
+ t = &w->body;
+ if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
+ return;
+ w->isscratch = FALSE;
+ if(n>=6 && runeeq(L"/guide", 6, name+(n-6), 6))
+ w->isscratch = TRUE;
+ else if(n>=7 && runeeq(L"+Errors", 7, name+(n-7), 7))
+ w->isscratch = TRUE;
+ filesetname(t->file, name, n);
+ for(i=0; i<t->file->ntext; i++){
+ v = t->file->text[i]->w;
+ winsettag(v);
+ v->isscratch = w->isscratch;
+ }
+}
+
+void
+wintype(Window *w, Text *t, Rune r)
+{
+ int i;
+
+ texttype(t, r);
+ if(t->what == Tag)
+ w->tagsafe = FALSE;
+ if(t->what == Body)
+ for(i=0; i<t->file->ntext; i++)
+ textscrdraw(t->file->text[i]);
+ winsettag(w);
+}
+
+void
+wincleartag(Window *w)
+{
+ int i, n;
+ Rune *r;
+
+ /* w must be committed */
+ n = w->tag.file->nc;
+ r = runemalloc(n);
+ bufread(w->tag.file, 0, r, n);
+ for(i=0; i<n; i++)
+ if(r[i]==' ' || r[i]=='\t')
+ break;
+ for(; i<n; i++)
+ if(r[i] == '|')
+ break;
+ if(i == n)
+ return;
+ i++;
+ textdelete(&w->tag, i, n, TRUE);
+ free(r);
+ w->tag.file->mod = FALSE;
+ if(w->tag.q0 > i)
+ w->tag.q0 = i;
+ if(w->tag.q1 > i)
+ w->tag.q1 = i;
+ textsetselect(&w->tag, w->tag.q0, w->tag.q1);
+}
+
+void
+winsettag1(Window *w)
+{
+ int i, j, k, n, bar, dirty;
+ Rune *new, *old, *r;
+ Image *b;
+ uint q0, q1;
+ Rectangle br;
+
+ /* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
+ if(w->tag.ncache!=0 || w->tag.file->mod)
+ wincommit(w, &w->tag); /* check file name; also guarantees we can modify tag contents */
+ old = runemalloc(w->tag.file->nc+1);
+ bufread(w->tag.file, 0, old, w->tag.file->nc);
+ old[w->tag.file->nc] = '\0';
+ for(i=0; i<w->tag.file->nc; i++)
+ if(old[i]==' ' || old[i]=='\t')
+ break;
+ if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
+ textdelete(&w->tag, 0, i, TRUE);
+ textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
+ free(old);
+ old = runemalloc(w->tag.file->nc+1);
+ bufread(w->tag.file, 0, old, w->tag.file->nc);
+ old[w->tag.file->nc] = '\0';
+ w->tagsafe = FALSE;
+ }
+ new = runemalloc(w->body.file->nname+100);
+ i = 0;
+ runemove(new+i, w->body.file->name, w->body.file->nname);
+ i += w->body.file->nname;
+ runemove(new+i, L" Del Snarf", 10);
+ i += 10;
+ if(w->filemenu){
+ if(w->body.file->delta.nc>0 || w->body.ncache){
+ runemove(new+i, L" Undo", 5);
+ i += 5;
+ }
+ if(w->body.file->epsilon.nc > 0){
+ runemove(new+i, L" Redo", 5);
+ i += 5;
+ }
+ dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
+ if(!w->isdir && dirty){
+ runemove(new+i, L" Put", 4);
+ i += 4;
+ }
+ }
+ if(w->isdir){
+ runemove(new+i, L" Get", 4);
+ i += 4;
+ }
+ runemove(new+i, L" |", 2);
+ i += 2;
+ r = runestrchr(old, '|');
+ if(r)
+ k = r-old+1;
+ else{
+ k = w->tag.file->nc;
+ if(w->body.file->seq == 0){
+ runemove(new+i, L" Look ", 6);
+ i += 6;
+ }
+ }
+
+ new[i] = 0;
+ /* replace tag if the new one is different */
+ if(runeeq(new, i, old, k) == FALSE){
+ n = k;
+ if(n > i)
+ n = i;
+ for(j=0; j<n; j++)
+ if(old[j] != new[j])
+ break;
+ q0 = w->tag.q0;
+ q1 = w->tag.q1;
+ textdelete(&w->tag, j, k, TRUE);
+ textinsert(&w->tag, j, new+j, i-j, TRUE);
+ /* try to preserve user selection */
+ r = runestrchr(old, '|');
+ if(r){
+ bar = r-old;
+ if(q0 > bar){
+ bar = (runestrchr(new, '|')-new)-bar;
+ w->tag.q0 = q0+bar;
+ w->tag.q1 = q1+bar;
+ }
+ }
+ w->tagsafe = FALSE;
+ }
+ free(old);
+ free(new);
+ w->tag.file->mod = FALSE;
+ n = w->tag.file->nc+w->tag.ncache;
+ if(w->tag.q0 > n)
+ w->tag.q0 = n;
+ if(w->tag.q1 > n)
+ w->tag.q1 = n;
+ textsetselect(&w->tag, w->tag.q0, w->tag.q1);
+ b = button;
+ if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
+ b = modbutton;
+ br.min = w->tag.scrollr.min;
+ br.max.x = br.min.x + Dx(b->r);
+ br.max.y = br.min.y + Dy(b->r);
+ draw(screen, br, b, nil, b->r.min);
+ if(w->tagsafe == FALSE)
+ winresize(w, w->r, TRUE);
+}
+
+void
+winsettag(Window *w)
+{
+ int i;
+ File *f;
+ Window *v;
+
+ f = w->body.file;
+ for(i=0; i<f->ntext; i++){
+ v = f->text[i]->w;
+ if(v->col->safe || v->body.maxlines>0)
+ winsettag1(v);
+ }
+}
+
+void
+wincommit(Window *w, Text *t)
+{
+ Rune *r;
+ int i;
+ File *f;
+
+ textcommit(t, TRUE);
+ f = t->file;
+ if(f->ntext > 1)
+ for(i=0; i<f->ntext; i++)
+ textcommit(f->text[i], FALSE); /* no-op for t */
+ if(t->what == Body)
+ return;
+ r = runemalloc(w->tag.file->nc);
+ bufread(w->tag.file, 0, r, w->tag.file->nc);
+ for(i=0; i<w->tag.file->nc; i++)
+ if(r[i]==' ' || r[i]=='\t')
+ break;
+ if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
+ seq++;
+ filemark(w->body.file);
+ w->body.file->mod = TRUE;
+ w->dirty = TRUE;
+ winsetname(w, r, i);
+ winsettag(w);
+ }
+ free(r);
+}
+
+void
+winaddincl(Window *w, Rune *r, int n)
+{
+ char *a;
+ Dir *d;
+ Runestr rs;
+
+ a = runetobyte(r, n);
+ d = dirstat(a);
+ if(d == nil){
+ if(a[0] == '/')
+ goto Rescue;
+ rs = dirname(&w->body, r, n);
+ r = rs.r;
+ n = rs.nr;
+ free(a);
+ a = runetobyte(r, n);
+ d = dirstat(a);
+ if(d == nil)
+ goto Rescue;
+ r = runerealloc(r, n+1);
+ r[n] = 0;
+ }
+ if((d->qid.type&QTDIR) == 0){
+ free(d);
+ warning(nil, "%s: not a directory\n", a);
+ free(r);
+ free(a);
+ return;
+ }
+ free(a);
+ free(d);
+ w->nincl++;
+ w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
+ memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
+ w->incl[0] = runemalloc(n+1);
+ runemove(w->incl[0], r, n);
+ free(r);
+ return;
+
+Rescue:
+ warning(nil, "%s: %r\n", a);
+ free(r);
+ free(a);
+ return;
+}
+
+int
+winclean(Window *w, int conservative)
+{
+ if(w->isscratch || w->isdir) /* don't whine if it's a guide file, error window, etc. */
+ return TRUE;
+ if(!conservative && w->nopen[QWevent]>0)
+ return TRUE;
+ if(w->dirty){
+ if(w->body.file->nname)
+ warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
+ else{
+ if(w->body.file->nc < 100) /* don't whine if it's too small */
+ return TRUE;
+ warning(nil, "unnamed file modified\n");
+ }
+ w->dirty = FALSE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+char*
+winctlprint(Window *w, char *buf, int fonts)
+{
+ sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->nc,
+ w->body.file->nc, w->isdir, w->dirty);
+ if(fonts)
+ return smprint("%s%11d %q %11d " , buf, Dx(w->body.r),
+ w->body.reffont->f->name, w->body.maxtab);
+ return buf;
+}
+
+void
+winevent(Window *w, char *fmt, ...)
+{
+ int n;
+ char *b;
+ Xfid *x;
+ va_list arg;
+
+ if(w->nopen[QWevent] == 0)
+ return;
+ if(w->owner == 0)
+ error("no window owner");
+ va_start(arg, fmt);
+ b = vsmprint(fmt, arg);
+ va_end(arg);
+ if(b == nil)
+ error("vsmprint failed");
+ n = strlen(b);
+ w->events = erealloc(w->events, w->nevents+1+n);
+ w->events[w->nevents++] = w->owner;
+ memmove(w->events+w->nevents, b, n);
+ free(b);
+ w->nevents += n;
+ x = w->eventx;
+ if(x){
+ w->eventx = nil;
+ sendp(x->c, nil);
+ }
+}
--- /dev/null
+++ b/xfid.c
@@ -1,0 +1,1082 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+ Ctlsize = 5*12
+};
+
+char Edel[] = "deleted window";
+char Ebadctl[] = "ill-formed control message";
+char Ebadaddr[] = "bad address syntax";
+char Eaddr[] = "address out of range";
+char Einuse[] = "already in use";
+char Ebadevent[] = "bad event syntax";
+extern char Eperm[];
+
+static
+void
+clampaddr(Window *w)
+{
+ if(w->addr.q0 < 0)
+ w->addr.q0 = 0;
+ if(w->addr.q1 < 0)
+ w->addr.q1 = 0;
+ if(w->addr.q0 > w->body.file->nc)
+ w->addr.q0 = w->body.file->nc;
+ if(w->addr.q1 > w->body.file->nc)
+ w->addr.q1 = w->body.file->nc;
+}
+
+void
+xfidctl(void *arg)
+{
+ Xfid *x;
+ void (*f)(Xfid*);
+
+ threadsetname("xfidctlthread");
+ x = arg;
+ for(;;){
+ f = recvp(x->c);
+ (*f)(x);
+ flushimage(display, 1);
+ sendp(cxfidfree, x);
+ }
+}
+
+void
+xfidflush(Xfid *x)
+{
+ Fcall fc;
+ int i, j;
+ Window *w;
+ Column *c;
+ Xfid *wx;
+
+ xfidlogflush(x);
+
+ /* search windows for matching tag */
+ qlock(&row);
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ winlock(w, 'E');
+ wx = w->eventx;
+ if(wx!=nil && wx->tag==x->oldtag){
+ w->eventx = nil;
+ wx->flushed = TRUE;
+ sendp(wx->c, nil);
+ winunlock(w);
+ goto out;
+ }
+ winunlock(w);
+ }
+ }
+out:
+ qunlock(&row);
+ respond(x, &fc, nil);
+}
+
+void
+xfidopen(Xfid *x)
+{
+ Fcall fc;
+ Window *w;
+ Text *t;
+ char *s;
+ Rune *r;
+ int m, n, q, q0, q1;
+
+ w = x->f->w;
+ t = &w->body;
+ q = FILE(x->f->qid);
+ if(w){
+ winlock(w, 'E');
+ switch(q){
+ case QWaddr:
+ if(w->nopen[q]++ == 0){
+ w->addr = (Range){0,0};
+ w->limit = (Range){-1,-1};
+ }
+ break;
+ case QWdata:
+ case QWxdata:
+ w->nopen[q]++;
+ break;
+ case QWevent:
+ if(w->nopen[q]++ == 0){
+ if(!w->isdir && w->col!=nil){
+ w->filemenu = FALSE;
+ winsettag(w);
+ }
+ }
+ break;
+ case QWrdsel:
+ /*
+ * Use a temporary file.
+ * A pipe would be the obvious, but we can't afford the
+ * broken pipe notification. Using the code to read QWbody
+ * is n², which should probably also be fixed. Even then,
+ * though, we'd need to squirrel away the data in case it's
+ * modified during the operation, e.g. by |sort
+ */
+ if(w->rdselfd >= 0){
+ winunlock(w);
+ respond(x, &fc, Einuse);
+ return;
+ }
+ w->rdselfd = tempfile();
+ if(w->rdselfd < 0){
+ winunlock(w);
+ respond(x, &fc, "can't create temp file");
+ return;
+ }
+ w->nopen[q]++;
+ q0 = t->q0;
+ q1 = t->q1;
+ r = fbufalloc();
+ s = fbufalloc();
+ while(q0 < q1){
+ n = q1 - q0;
+ if(n > (BUFSIZE-1)/UTFmax)
+ n = (BUFSIZE-1)/UTFmax;
+ bufread(t->file, q0, r, n);
+ m = snprint(s, BUFSIZE, "%.*S", n, r);
+ if(write(w->rdselfd, s, m) != m){
+ warning(nil, "can't write temp file for pipe command %r\n");
+ break;
+ }
+ q0 += n;
+ }
+ fbuffree(s);
+ fbuffree(r);
+ break;
+ case QWwrsel:
+ w->nopen[q]++;
+ seq++;
+ filemark(t->file);
+ cut(t, t, nil, FALSE, TRUE, nil, 0);
+ w->wrselrange = (Range){t->q1, t->q1};
+ w->nomark = TRUE;
+ break;
+ case QWeditout:
+ if(editing == FALSE){
+ winunlock(w);
+ respond(x, &fc, Eperm);
+ return;
+ }
+ w->wrselrange = (Range){t->q1, t->q1};
+ break;
+ }
+ winunlock(w);
+ }
+ else{
+ switch(q){
+ case Qlog:
+ xfidlogopen(x);
+ break;
+ }
+ }
+ fc.qid = x->f->qid;
+ fc.iounit = messagesize-IOHDRSZ;
+ x->f->open = TRUE;
+ respond(x, &fc, nil);
+}
+
+void
+xfidclose(Xfid *x)
+{
+ Fcall fc;
+ Window *w;
+ int q;
+ Text *t;
+
+ w = x->f->w;
+ x->f->busy = FALSE;
+ if(x->f->open == FALSE){
+ if(w != nil)
+ winclose(w);
+ respond(x, &fc, nil);
+ return;
+ }
+
+ x->f->open = FALSE;
+ if(w){
+ winlock(w, 'E');
+ q = FILE(x->f->qid);
+ switch(q){
+ case QWctl:
+ if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
+ w->ctlfid = ~0;
+ qunlock(&w->ctllock);
+ }
+ break;
+ case QWdata:
+ case QWxdata:
+ w->nomark = FALSE;
+ /* fall through */
+ case QWaddr:
+ case QWevent: /* BUG: do we need to shut down Xfid? */
+ if(--w->nopen[q] == 0){
+ if(q == QWdata || q == QWxdata)
+ w->nomark = FALSE;
+ if(q==QWevent && !w->isdir && w->col!=nil){
+ w->filemenu = TRUE;
+ winsettag(w);
+ }
+ if(q == QWevent){
+ free(w->dumpstr);
+ free(w->dumpdir);
+ w->dumpstr = nil;
+ w->dumpdir = nil;
+ }
+ }
+ break;
+ case QWrdsel:
+ close(w->rdselfd);
+ w->rdselfd = -1;
+ break;
+ case QWwrsel:
+ w->nomark = FALSE;
+ t = &w->body;
+ /* before: only did this if !w->noscroll, but that didn't seem right in practice */
+ textshow(t, min(w->wrselrange.q0, t->file->nc),
+ min(w->wrselrange.q1, t->file->nc), 1);
+ textscrdraw(t);
+ break;
+ }
+ winunlock(w);
+ winclose(w);
+ }
+ respond(x, &fc, nil);
+}
+
+void
+xfidread(Xfid *x)
+{
+ Fcall fc;
+ int n, q;
+ uint off;
+ char *b;
+ char buf[256];
+ Window *w;
+
+ q = FILE(x->f->qid);
+ w = x->f->w;
+ if(w == nil){
+ fc.count = 0;
+ switch(q){
+ case Qcons:
+ case Qlabel:
+ break;
+ case Qindex:
+ xfidindexread(x);
+ return;
+ case Qlog:
+ xfidlogread(x);
+ return;
+ default:
+ warning(nil, "unknown qid %d\n", q);
+ break;
+ }
+ respond(x, &fc, nil);
+ return;
+ }
+ winlock(w, 'F');
+ if(w->col == nil){
+ winunlock(w);
+ respond(x, &fc, Edel);
+ return;
+ }
+ off = x->offset;
+ switch(q){
+ case QWaddr:
+ textcommit(&w->body, TRUE);
+ clampaddr(w);
+ sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1);
+ goto Readbuf;
+
+ case QWbody:
+ xfidutfread(x, &w->body, w->body.file->nc, QWbody);
+ break;
+
+ case QWctl:
+ b = winctlprint(w, buf, 1);
+ goto Readb;
+
+ Readbuf:
+ b = buf;
+ Readb:
+ n = strlen(b);
+ if(off > n)
+ off = n;
+ if(off+x->count > n)
+ x->count = n-off;
+ fc.count = x->count;
+ fc.data = b+off;
+ respond(x, &fc, nil);
+ if(b != buf)
+ free(b);
+ break;
+
+ case QWevent:
+ xfideventread(x, w);
+ break;
+
+ case QWdata:
+ /* BUG: what should happen if q1 > q0? */
+ if(w->addr.q0 > w->body.file->nc){
+ respond(x, &fc, Eaddr);
+ break;
+ }
+ w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->nc);
+ w->addr.q1 = w->addr.q0;
+ break;
+
+ case QWxdata:
+ /* BUG: what should happen if q1 > q0? */
+ if(w->addr.q0 > w->body.file->nc){
+ respond(x, &fc, Eaddr);
+ break;
+ }
+ w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->addr.q1);
+ break;
+
+ case QWtag:
+ xfidutfread(x, &w->tag, w->tag.file->nc, QWtag);
+ break;
+
+ case QWrdsel:
+ seek(w->rdselfd, off, 0);
+ n = x->count;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ b = fbufalloc();
+ n = read(w->rdselfd, b, n);
+ if(n < 0){
+ respond(x, &fc, "I/O error in temp file");
+ break;
+ }
+ fc.count = n;
+ fc.data = b;
+ respond(x, &fc, nil);
+ fbuffree(b);
+ break;
+
+ default:
+ sprint(buf, "unknown qid %d in read", q);
+ respond(x, &fc, nil);
+ }
+ winunlock(w);
+}
+
+static Rune*
+fullrunewrite(Xfid *x, int *inr)
+{
+ int q, cnt, c, nb, nr;
+ Rune *r;
+
+ q = x->f->nrpart;
+ cnt = x->count;
+ if(q > 0){
+ memmove(x->data+q, x->data, cnt); /* there's room; see fsysproc */
+ memmove(x->data, x->f->rpart, q);
+ cnt += q;
+ x->f->nrpart = 0;
+ }
+ r = runemalloc(cnt);
+ cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
+ /* approach end of buffer */
+ while(fullrune(x->data+nb, cnt-nb)){
+ c = nb;
+ nb += chartorune(&r[nr], x->data+c);
+ if(r[nr])
+ nr++;
+ }
+ if(nb < cnt){
+ memmove(x->f->rpart, x->data+nb, cnt-nb);
+ x->f->nrpart = cnt-nb;
+ }
+ *inr = nr;
+ return r;
+}
+
+void
+xfidwrite(Xfid *x)
+{
+ Fcall fc;
+ int c, qid, nb, nr, eval;
+ char buf[64], *err;
+ Window *w;
+ Rune *r;
+ Range a;
+ Text *t;
+ uint q0, tq0, tq1;
+
+ qid = FILE(x->f->qid);
+ w = x->f->w;
+ if(w){
+ c = 'F';
+ if(qid==QWtag || qid==QWbody)
+ c = 'E';
+ winlock(w, c);
+ if(w->col == nil){
+ winunlock(w);
+ respond(x, &fc, Edel);
+ return;
+ }
+ }
+ x->data[x->count] = 0;
+ switch(qid){
+ case Qcons:
+ w = errorwin(x->f->mntdir, 'X');
+ t=&w->body;
+ goto BodyTag;
+
+ case Qlabel:
+ fc.count = x->count;
+ respond(x, &fc, nil);
+ break;
+
+ case QWaddr:
+ x->data[x->count] = 0;
+ r = bytetorune(x->data, &nr);
+ t = &w->body;
+ wincommit(w, t);
+ eval = TRUE;
+ a = address(x->f->mntdir, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
+ free(r);
+ if(nb < nr){
+ respond(x, &fc, Ebadaddr);
+ break;
+ }
+ if(!eval){
+ respond(x, &fc, Eaddr);
+ break;
+ }
+ w->addr = a;
+ fc.count = x->count;
+ respond(x, &fc, nil);
+ break;
+
+ case Qeditout:
+ case QWeditout:
+ r = fullrunewrite(x, &nr);
+ if(w)
+ err = edittext(w, w->wrselrange.q1, r, nr);
+ else
+ err = edittext(nil, 0, r, nr);
+ free(r);
+ if(err != nil){
+ respond(x, &fc, err);
+ break;
+ }
+ fc.count = x->count;
+ respond(x, &fc, nil);
+ break;
+
+ case QWerrors:
+ w = errorwinforwin(w);
+ t = &w->body;
+ goto BodyTag;
+
+ case QWbody:
+ case QWwrsel:
+ t = &w->body;
+ goto BodyTag;
+
+ case QWctl:
+ xfidctlwrite(x, w);
+ break;
+
+ case QWdata:
+ a = w->addr;
+ t = &w->body;
+ wincommit(w, t);
+ if(a.q0>t->file->nc || a.q1>t->file->nc){
+ respond(x, &fc, Eaddr);
+ break;
+ }
+ r = runemalloc(x->count);
+ cvttorunes(x->data, x->count, r, &nb, &nr, nil);
+ if(w->nomark == FALSE){
+ seq++;
+ filemark(t->file);
+ }
+ q0 = a.q0;
+ if(a.q1 > q0){
+ textdelete(t, q0, a.q1, TRUE);
+ w->addr.q1 = q0;
+ }
+ tq0 = t->q0;
+ tq1 = t->q1;
+ textinsert(t, q0, r, nr, TRUE);
+ if(tq0 >= q0)
+ tq0 += nr;
+ if(tq1 >= q0)
+ tq1 += nr;
+ textsetselect(t, tq0, tq1);
+ if(!t->w->noscroll)
+ textshow(t, q0, q0+nr, 0);
+ textscrdraw(t);
+ winsettag(w);
+ free(r);
+ w->addr.q0 += nr;
+ w->addr.q1 = w->addr.q0;
+ fc.count = x->count;
+ respond(x, &fc, nil);
+ break;
+
+ case QWevent:
+ xfideventwrite(x, w);
+ break;
+
+ case QWtag:
+ t = &w->tag;
+ goto BodyTag;
+
+ BodyTag:
+ r = fullrunewrite(x, &nr);
+ if(nr > 0){
+ wincommit(w, t);
+ if(qid == QWwrsel){
+ q0 = w->wrselrange.q1;
+ if(q0 > t->file->nc)
+ q0 = t->file->nc;
+ }else
+ q0 = t->file->nc;
+ if(qid == QWtag)
+ textinsert(t, q0, r, nr, TRUE);
+ else{
+ if(w->nomark == FALSE){
+ seq++;
+ filemark(t->file);
+ }
+ q0 = textbsinsert(t, q0, r, nr, TRUE, &nr);
+ textsetselect(t, t->q0, t->q1); /* insert could leave it somewhere else */
+ if(qid!=QWwrsel && !t->w->noscroll)
+ textshow(t, q0+nr, q0+nr, 1);
+ textscrdraw(t);
+ }
+ winsettag(w);
+ if(qid == QWwrsel)
+ w->wrselrange.q1 += nr;
+ free(r);
+ }
+ fc.count = x->count;
+ respond(x, &fc, nil);
+ break;
+
+ default:
+ sprint(buf, "unknown qid %d in write", qid);
+ respond(x, &fc, buf);
+ break;
+ }
+ if(w)
+ winunlock(w);
+}
+
+void
+xfidctlwrite(Xfid *x, Window *w)
+{
+ Fcall fc;
+ int i, m, n, nb, nr, nulls;
+ Rune *r;
+ char *err, *p, *pp, *q, *e;
+ int scrdraw, settag;
+ Text *t;
+
+ err = nil;
+ e = x->data+x->count;
+ scrdraw = FALSE;
+ settag = FALSE;
+ r = emalloc(x->count*UTFmax+1);
+ x->data[x->count] = 0;
+ textcommit(&w->tag, TRUE);
+ for(n=0; n<x->count; n+=m){
+ p = x->data+n;
+ if(strncmp(p, "lock", 4) == 0){ /* make window exclusive use */
+ qlock(&w->ctllock);
+ w->ctlfid = x->f->fid;
+ m = 4;
+ }else
+ if(strncmp(p, "unlock", 6) == 0){ /* release exclusive use */
+ w->ctlfid = ~0;
+ qunlock(&w->ctllock);
+ m = 6;
+ }else
+ if(strncmp(p, "clean", 5) == 0){ /* mark window 'clean', seq=0 */
+ t = &w->body;
+ t->eq0 = ~0;
+ filereset(t->file);
+ t->file->mod = FALSE;
+ w->dirty = FALSE;
+ settag = TRUE;
+ m = 5;
+ }else
+ if(strncmp(p, "dirty", 5) == 0){ /* mark window 'dirty' */
+ t = &w->body;
+ /* doesn't change sequence number, so "Put" won't appear. it shouldn't. */
+ t->file->mod = TRUE;
+ w->dirty = TRUE;
+ settag = TRUE;
+ m = 5;
+ }else
+ if(strncmp(p, "show", 4) == 0){ /* show dot */
+ t = &w->body;
+ textshow(t, t->q0, t->q1, 1);
+ m = 4;
+ }else
+ if(strncmp(p, "name ", 5) == 0){ /* set file name */
+ pp = p+5;
+ m = 5;
+ q = memchr(pp, '\n', e-pp);
+ if(q==nil || q==pp){
+ err = Ebadctl;
+ break;
+ }
+ *q = 0;
+ nulls = FALSE;
+ cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+ if(nulls){
+ err = "nulls in file name";
+ break;
+ }
+ for(i=0; i<nr; i++)
+ if(r[i] <= ' '){
+ err = "bad character in file name";
+ goto out;
+ }
+out:
+ seq++;
+ filemark(w->body.file);
+ winsetname(w, r, nr);
+ m += (q+1) - pp;
+ }else
+ if(strncmp(p, "dump ", 5) == 0){ /* set dump string */
+ pp = p+5;
+ m = 5;
+ q = memchr(pp, '\n', e-pp);
+ if(q==nil || q==pp){
+ err = Ebadctl;
+ break;
+ }
+ *q = 0;
+ nulls = FALSE;
+ cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+ if(nulls){
+ err = "nulls in dump string";
+ break;
+ }
+ w->dumpstr = runetobyte(r, nr);
+ m += (q+1) - pp;
+ }else
+ if(strncmp(p, "dumpdir ", 8) == 0){ /* set dump directory */
+ pp = p+8;
+ m = 8;
+ q = memchr(pp, '\n', e-pp);
+ if(q==nil || q==pp){
+ err = Ebadctl;
+ break;
+ }
+ *q = 0;
+ nulls = FALSE;
+ cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+ if(nulls){
+ err = "nulls in dump directory string";
+ break;
+ }
+ w->dumpdir = runetobyte(r, nr);
+ m += (q+1) - pp;
+ }else
+ if(strncmp(p, "delete", 6) == 0){ /* delete for sure */
+ colclose(w->col, w, TRUE);
+ m = 6;
+ }else
+ if(strncmp(p, "del", 3) == 0){ /* delete, but check dirty */
+ if(!winclean(w, TRUE)){
+ err = "file dirty";
+ break;
+ }
+ colclose(w->col, w, TRUE);
+ m = 3;
+ }else
+ if(strncmp(p, "get", 3) == 0){ /* get file */
+ get(&w->body, nil, nil, FALSE, XXX, nil, 0);
+ m = 3;
+ }else
+ if(strncmp(p, "put", 3) == 0){ /* put file */
+ put(&w->body, nil, nil, XXX, XXX, nil, 0);
+ m = 3;
+ }else
+ if(strncmp(p, "dot=addr", 8) == 0){ /* set dot */
+ textcommit(&w->body, TRUE);
+ clampaddr(w);
+ w->body.q0 = w->addr.q0;
+ w->body.q1 = w->addr.q1;
+ textsetselect(&w->body, w->body.q0, w->body.q1);
+ settag = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "addr=dot", 8) == 0){ /* set addr */
+ w->addr.q0 = w->body.q0;
+ w->addr.q1 = w->body.q1;
+ m = 8;
+ }else
+ if(strncmp(p, "limit=addr", 10) == 0){ /* set limit */
+ textcommit(&w->body, TRUE);
+ clampaddr(w);
+ w->limit.q0 = w->addr.q0;
+ w->limit.q1 = w->addr.q1;
+ m = 10;
+ }else
+ if(strncmp(p, "nomark", 6) == 0){ /* turn off automatic marking */
+ w->nomark = TRUE;
+ m = 6;
+ }else
+ if(strncmp(p, "mark", 4) == 0){ /* mark file */
+ seq++;
+ filemark(w->body.file);
+ settag = TRUE;
+ m = 4;
+ }else
+ if(strncmp(p, "nomenu", 6) == 0){ /* turn off automatic menu */
+ w->filemenu = FALSE;
+ m = 6;
+ }else
+ if(strncmp(p, "menu", 4) == 0){ /* enable automatic menu */
+ w->filemenu = TRUE;
+ m = 4;
+ }else
+ if(strncmp(p, "noscroll", 8) == 0){ /* turn off automatic scrolling */
+ w->noscroll = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "cleartag", 8) == 0){ /* wipe tag right of bar */
+ wincleartag(w);
+ settag = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "scroll", 6) == 0){ /* turn on automatic scrolling (writes to body only) */
+ w->noscroll = FALSE;
+ m = 6;
+ }else
+ if(strncmp(p, "scratch", 7) == 0){ /* mark as a scratch file */
+ w->isscratch = TRUE;
+ m = 7;
+ }else{
+ err = Ebadctl;
+ break;
+ }
+ while(p[m] == '\n')
+ m++;
+ }
+
+ free(r);
+ if(err)
+ n = 0;
+ fc.count = n;
+ respond(x, &fc, err);
+ if(settag)
+ winsettag(w);
+ if(scrdraw)
+ textscrdraw(&w->body);
+}
+
+void
+xfideventwrite(Xfid *x, Window *w)
+{
+ Fcall fc;
+ int m, n;
+ Rune *r;
+ char *err, *p, *q;
+ Text *t;
+ int c;
+ uint q0, q1;
+
+ err = nil;
+ r = emalloc(x->count*UTFmax+1);
+ for(n=0; n<x->count; n+=m){
+ p = x->data+n;
+ w->owner = *p++; /* disgusting */
+ c = *p++;
+ while(*p == ' ')
+ p++;
+ q0 = strtoul(p, &q, 10);
+ if(q == p)
+ goto Rescue;
+ p = q;
+ while(*p == ' ')
+ p++;
+ q1 = strtoul(p, &q, 10);
+ if(q == p)
+ goto Rescue;
+ p = q;
+ while(*p == ' ')
+ p++;
+ if(*p++ != '\n')
+ goto Rescue;
+ m = p-(x->data+n);
+ if('a'<=c && c<='z')
+ t = &w->tag;
+ else if('A'<=c && c<='Z')
+ t = &w->body;
+ else
+ goto Rescue;
+ if(q0>t->file->nc || q1>t->file->nc || q0>q1)
+ goto Rescue;
+
+ qlock(&row); /* just like mousethread */
+ switch(c){
+ case 'x':
+ case 'X':
+ execute(t, q0, q1, TRUE, nil);
+ break;
+ case 'l':
+ case 'L':
+ look3(t, q0, q1, TRUE);
+ break;
+ default:
+ qunlock(&row);
+ goto Rescue;
+ }
+ qunlock(&row);
+
+ }
+
+ Out:
+ free(r);
+ if(err)
+ n = 0;
+ fc.count = n;
+ respond(x, &fc, err);
+ return;
+
+ Rescue:
+ err = Ebadevent;
+ goto Out;
+}
+
+void
+xfidutfread(Xfid *x, Text *t, uint q1, int qid)
+{
+ Fcall fc;
+ Window *w;
+ Rune *r;
+ char *b, *b1;
+ uint q, off, boff;
+ int m, n, nr, nb;
+
+ w = t->w;
+ wincommit(w, t);
+ off = x->offset;
+ r = fbufalloc();
+ b = fbufalloc();
+ b1 = emalloc(x->count);
+ n = 0;
+ if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){
+ boff = w->utflastboff;
+ q = w->utflastq;
+ }else{
+ /* BUG: stupid code: scan from beginning */
+ boff = 0;
+ q = 0;
+ }
+ w->utflastqid = qid;
+ while(q<q1 && n<x->count){
+ /*
+ * Updating here avoids partial rune problem: we're always on a
+ * char boundary. The cost is we will usually do one more read
+ * than we really need, but that's better than being n^2.
+ */
+ w->utflastboff = boff;
+ w->utflastq = q;
+ nr = q1-q;
+ if(nr > (BUFSIZE-1)/UTFmax)
+ nr = (BUFSIZE-1)/UTFmax;
+ bufread(t->file, q, r, nr);
+ nb = snprint(b, BUFSIZE, "%.*S", nr, r);
+ if(boff >= off){
+ m = nb;
+ if(boff+m > off+x->count)
+ m = off+x->count - boff;
+ memmove(b1+n, b, m);
+ n += m;
+ }else if(boff+nb > off){
+ if(n != 0)
+ error("bad count in utfrune");
+ m = nb - (off-boff);
+ if(m > x->count)
+ m = x->count;
+ memmove(b1, b+(off-boff), m);
+ n += m;
+ }
+ boff += nb;
+ q += nr;
+ }
+ fbuffree(r);
+ fbuffree(b);
+ fc.count = n;
+ fc.data = b1;
+ respond(x, &fc, nil);
+ free(b1);
+}
+
+int
+xfidruneread(Xfid *x, Text *t, uint q0, uint q1)
+{
+ Fcall fc;
+ Window *w;
+ Rune *r, junk;
+ char *b, *b1;
+ uint q, boff;
+ int i, rw, m, n, nr, nb;
+
+ w = t->w;
+ wincommit(w, t);
+ r = fbufalloc();
+ b = fbufalloc();
+ b1 = emalloc(x->count);
+ n = 0;
+ q = q0;
+ boff = 0;
+ while(q<q1 && n<x->count){
+ nr = q1-q;
+ if(nr > (BUFSIZE-1)/UTFmax)
+ nr = (BUFSIZE-1)/UTFmax;
+ bufread(t->file, q, r, nr);
+ nb = snprint(b, BUFSIZE, "%.*S", nr, r);
+ m = nb;
+ if(boff+m > x->count){
+ i = x->count - boff;
+ /* copy whole runes only */
+ m = 0;
+ nr = 0;
+ while(m < i){
+ rw = chartorune(&junk, b+m);
+ if(m+rw > i)
+ break;
+ m += rw;
+ nr++;
+ }
+ if(m == 0)
+ break;
+ }
+ memmove(b1+n, b, m);
+ n += m;
+ boff += nb;
+ q += nr;
+ }
+ fbuffree(r);
+ fbuffree(b);
+ fc.count = n;
+ fc.data = b1;
+ respond(x, &fc, nil);
+ free(b1);
+ return q-q0;
+}
+
+void
+xfideventread(Xfid *x, Window *w)
+{
+ Fcall fc;
+ char *b;
+ int i, n;
+
+ i = 0;
+ x->flushed = FALSE;
+ while(w->nevents == 0){
+ if(i){
+ if(!x->flushed)
+ respond(x, &fc, "window shut down");
+ return;
+ }
+ w->eventx = x;
+ winunlock(w);
+ recvp(x->c);
+ winlock(w, 'F');
+ i++;
+ }
+
+ n = w->nevents;
+ if(n > x->count)
+ n = x->count;
+ fc.count = n;
+ fc.data = w->events;
+ respond(x, &fc, nil);
+ b = w->events;
+ w->events = estrdup(w->events+n);
+ free(b);
+ w->nevents -= n;
+}
+
+void
+xfidindexread(Xfid *x)
+{
+ Fcall fc;
+ int i, j, m, n, nmax, isbuf, cnt, off;
+ Window *w;
+ char *b;
+ Rune *r;
+ Column *c;
+
+ qlock(&row);
+ nmax = 0;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ nmax += Ctlsize + w->tag.file->nc*UTFmax + 1;
+ }
+ }
+ nmax++;
+ isbuf = (nmax<=RBUFSIZE);
+ if(isbuf)
+ b = (char*)x->buf;
+ else
+ b = emalloc(nmax);
+ r = fbufalloc();
+ n = 0;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ /* only show the currently active window of a set */
+ if(w->body.file->curtext != &w->body)
+ continue;
+ winctlprint(w, b+n, 0);
+ n += Ctlsize;
+ m = min(RBUFSIZE, w->tag.file->nc);
+ bufread(w->tag.file, 0, r, m);
+ m = n + snprint(b+n, nmax-n-1, "%.*S", m, r);
+ while(n<m && b[n]!='\n')
+ n++;
+ b[n++] = '\n';
+ }
+ }
+ qunlock(&row);
+ off = x->offset;
+ cnt = x->count;
+ if(off > n)
+ off = n;
+ if(off+cnt > n)
+ cnt = n-off;
+ fc.count = cnt;
+ memmove(r, b+off, cnt);
+ fc.data = (char*)r;
+ if(!isbuf)
+ free(b);
+ respond(x, &fc, nil);
+ fbuffree(r);
+}