Commit Diff


commit - 32f69c36e0eec1227934bbd34854bfebd88686f2
commit + b3994ec5c78e6c18885079b58abb7fb997899c3f
blob - /dev/null
blob + d9412c4ec0fba685f0bcc1670dcac0260ebd8508 (mode 644)
--- /dev/null
+++ src/cmd/acme/acme.c
@@ -0,0 +1,949 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+	/* for generating syms in mkfile only: */
+	#include <bio.h>
+	#include "edit.h"
+
+void	mousethread(void*);
+void	keyboardthread(void*);
+void	waitthread(void*);
+void	xfidallocthread(void*);
+void	newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont	**fontcache;
+int		nfontcache;
+char		wdir[512] = ".";
+Reffont	*reffonts[2];
+int		snarffd = -1;
+int		mainpid;
+int		plumbsendfd;
+int		plumbeditfd;
+
+enum{
+	NSnarf = 1000	/* less than 1024, I/O buffer size */
+};
+Rune	snarfrune[NSnarf+1];
+
+char		*fontnames[2] =
+{
+	"/lib/font/bit/lucidasans/euro.8.font",
+	"/lib/font/bit/lucm/unicode.9.font",
+};
+
+Command *command;
+
+void	acmeerrorinit(void);
+void	readfile(Column*, char*);
+int	shutdown(void*, char*);
+
+void
+derror(Display *d, char *errorstr)
+{
+	USED(d);
+	error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	int i;
+	char *p, *loadfile;
+	char buf[256];
+	Column *c;
+	int ncol;
+	Display *d;
+
+	rfork(RFENVG|RFNAMEG);
+
+	ncol = -1;
+
+	loadfile = nil;
+	ARGBEGIN{
+	case 'b':
+		bartflag = TRUE;
+		break;
+	case 'c':
+		p = ARGF();
+		if(p == nil)
+			goto Usage;
+		ncol = atoi(p);
+		if(ncol <= 0)
+			goto Usage;
+		break;
+	case 'f':
+		fontnames[0] = ARGF();
+		if(fontnames[0] == nil)
+			goto Usage;
+		break;
+	case 'F':
+		fontnames[1] = ARGF();
+		if(fontnames[1] == nil)
+			goto Usage;
+		break;
+	case 'l':
+		loadfile = ARGF();
+		if(loadfile == nil)
+			goto Usage;
+		break;
+	default:
+	Usage:
+		fprint(2, "usage: acme -c ncol -f fontname -F fixedwidthfontname -l loadfile\n");
+		exits("usage");
+	}ARGEND
+
+	cputype = getenv("cputype");
+	objtype = getenv("objtype");
+	home = getenv("home");
+	p = getenv("tabstop");
+	if(p != nil){
+		maxtab = strtoul(p, nil, 0);
+		free(p);
+	}
+	if(maxtab == 0)
+		maxtab = 4; 
+	if(loadfile)
+		rowloadfonts(loadfile);
+	putenv("font", fontnames[0]);
+	snarffd = open("/dev/snarf", OREAD|OCEXEC);
+/*
+	if(cputype){
+		sprint(buf, "/acme/bin/%s", cputype);
+		bind(buf, "/bin", MBEFORE);
+	}
+	bind("/acme/bin", "/bin", MBEFORE);
+*/
+	getwd(wdir, sizeof wdir);
+
+/*
+	if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+		fprint(2, "acme: can't open display: %r\n");
+		exits("geninitdraw");
+	}
+*/
+	if(initdraw(derror, fontnames[0], "acme") < 0){
+		fprint(2, "acme: can't open display: %r\n");
+		exits("initdraw");
+	}
+
+	d = display;
+	font = d->defaultfont;
+
+	reffont.f = font;
+	reffonts[0] = &reffont;
+	incref(&reffont.ref);	/* one to hold up 'font' variable */
+	incref(&reffont.ref);	/* one to hold up reffonts[0] */
+	fontcache = emalloc(sizeof(Reffont*));
+	nfontcache = 1;
+	fontcache[0] = &reffont;
+
+	iconinit();
+	timerinit();
+	rxinit();
+
+	cwait = threadwaitchan();
+	ccommand = chancreate(sizeof(Command**), 0);
+	ckill = chancreate(sizeof(Rune*), 0);
+	cxfidalloc = chancreate(sizeof(Xfid*), 0);
+	cxfidfree = chancreate(sizeof(Xfid*), 0);
+	cnewwindow = chancreate(sizeof(Channel*), 0);
+	cerr = chancreate(sizeof(char*), 0);
+	cedit = chancreate(sizeof(int), 0);
+	cexit = chancreate(sizeof(int), 0);
+	if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil){
+		fprint(2, "acme: can't create initial channels: %r\n");
+		exits("channels");
+	}
+
+	mousectl = initmouse(nil, screen);
+	if(mousectl == nil){
+		fprint(2, "acme: can't initialize mouse: %r\n");
+		exits("mouse");
+	}
+	mouse = &mousectl->m;
+	keyboardctl = initkeyboard(nil);
+	if(keyboardctl == nil){
+		fprint(2, "acme: can't initialize keyboard: %r\n");
+		exits("keyboard");
+	}
+	mainpid = getpid();
+	plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+	if(plumbeditfd < 0)
+		fprint(2, "acme: can't initialize plumber: %r\n");
+	else{
+		cplumb = chancreate(sizeof(Plumbmsg*), 0);
+		proccreate(plumbproc, nil, STACK);
+	}
+	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+	fsysinit();
+
+	#define	WPERCOL	8
+	disk = diskinit();
+	if(loadfile)
+		rowload(&row, loadfile, TRUE);
+	else{
+		rowinit(&row, screen->clipr);
+		if(ncol < 0){
+			if(argc == 0)
+				ncol = 2;
+			else{
+				ncol = (argc+(WPERCOL-1))/WPERCOL;
+				if(ncol < 2)
+					ncol = 2;
+			}
+		}
+		if(ncol == 0)
+			ncol = 2;
+		for(i=0; i<ncol; i++){
+			c = rowadd(&row, nil, -1);
+			if(c==nil && i==0)
+				error("initializing columns");
+		}
+		c = row.col[row.ncol-1];
+		if(argc == 0)
+			readfile(c, wdir);
+		else
+			for(i=0; i<argc; i++){
+				p = utfrrune(argv[i], '/');
+				if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+					readfile(c, argv[i]);
+				else
+					readfile(row.col[i/WPERCOL], argv[i]);
+			}
+	}
+	flushimage(display, 1);
+
+	acmeerrorinit();
+	threadcreate(keyboardthread, nil, STACK);
+	threadcreate(mousethread, nil, STACK);
+	threadcreate(waitthread, nil, STACK);
+	threadcreate(xfidallocthread, nil, STACK);
+	threadcreate(newwindowthread, nil, STACK);
+
+	threadnotify(shutdown, 1);
+	recvul(cexit);
+	killprocs();
+	threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+	Window *w;
+	Rune rb[256];
+	int nb, nr;
+	Runestr rs;
+
+	w = coladd(c, nil, nil, -1);
+	cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+	rs = cleanrname((Runestr){rb, nr});
+	winsetname(w, rs.r, rs.nr);
+	textload(&w->body, 0, s, 1);
+	w->body.file->mod = FALSE;
+	w->dirty = FALSE;
+	winsettag(w);
+	textscrdraw(&w->body);
+	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
+}
+
+char *oknotes[] ={
+	"delete",
+	"hangup",
+	"kill",
+	"exit",
+	nil
+};
+
+int	dumping;
+
+int
+shutdown(void *v, char *msg)
+{
+	int i;
+
+	if(strcmp(msg, "sys: write on closed pipe") == 0)
+		return 1;
+
+	USED(v);
+	killprocs();
+	if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+		dumping = TRUE;
+		rowdump(&row, nil);
+	}
+	for(i=0; oknotes[i]; i++)
+		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+			threadexitsall(msg);
+	print("acme: %s\n", msg);
+	abort();
+	return 0;
+}
+
+void
+killprocs(void)
+{
+	Command *c;
+
+	fsysclose();
+//	if(display)
+//		flushimage(display, 1);
+
+	for(c=command; c; c=c->next)
+		postnote(PNGROUP, c->pid, "hangup");
+}
+
+static int errorfd;
+int erroutfd;
+
+void
+acmeerrorproc(void *v)
+{
+	char *buf;
+	int n;
+
+	USED(v);
+	threadsetname("acmeerrorproc");
+	buf = emalloc(8192+1);
+	while((n=read(errorfd, buf, 8192)) >= 0){
+		buf[n] = '\0';
+		sendp(cerr, estrdup(buf));
+	}
+}
+
+void
+acmeerrorinit(void)
+{
+	int fd, pfd[2];
+	char buf[64];
+
+	if(pipe(pfd) < 0)
+		error("can't create pipe");
+#if 0
+	sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+	fd = create(acmeerrorfile, OWRITE, 0666);
+	if(fd < 0){
+		remove(acmeerrorfile);
+  		fd = create(acmeerrorfile, OWRITE, 0666);
+		if(fd < 0)
+			error("can't create acmeerror file");
+	}
+	sprint(buf, "%d", pfd[0]);
+	write(fd, buf, strlen(buf));
+	close(fd);
+	/* reopen pfd[1] close on exec */
+	sprint(buf, "/fd/%d", pfd[1]);
+	errorfd = open(buf, OREAD|OCEXEC);
+#endif
+	fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
+	fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
+	erroutfd = pfd[0];
+	errorfd = pfd[1];
+	if(errorfd < 0)
+		error("can't re-open acmeerror file");
+	proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *v)
+{
+	Plumbmsg *m;
+
+	USED(v);
+	threadsetname("plumbproc");
+	for(;;){
+		m = plumbrecv(plumbeditfd);
+		if(m == nil)
+			threadexits(nil);
+		sendp(cplumb, m);
+	}
+}
+
+void
+keyboardthread(void *v)
+{
+	Rune r;
+	Timer *timer;
+	Text *t;
+	enum { KTimer, KKey, NKALT };
+	static Alt alts[NKALT+1];
+
+	USED(v);
+	alts[KTimer].c = nil;
+	alts[KTimer].v = nil;
+	alts[KTimer].op = CHANNOP;
+	alts[KKey].c = keyboardctl->c;
+	alts[KKey].v = &r;
+	alts[KKey].op = CHANRCV;
+	alts[NKALT].op = CHANEND;
+
+	timer = nil;
+	typetext = nil;
+	threadsetname("keyboardthread");
+	for(;;){
+		switch(alt(alts)){
+		case KTimer:
+			timerstop(timer);
+			t = typetext;
+			if(t!=nil && t->what==Tag){
+				winlock(t->w, 'K');
+				wincommit(t->w, t);
+				winunlock(t->w);
+				flushimage(display, 1);
+			}
+			alts[KTimer].c = nil;
+			alts[KTimer].op = CHANNOP;
+			break;
+		case KKey:
+		casekeyboard:
+			typetext = rowtype(&row, r, mouse->xy);
+			t = typetext;
+			if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright))	/* scrolling doesn't change activecol */
+				activecol = t->col;
+			if(t!=nil && t->w!=nil)
+				t->w->body.file->curtext = &t->w->body;
+			if(timer != nil)
+				timercancel(timer);
+			if(t!=nil && t->what==Tag) {
+				timer = timerstart(500);
+				alts[KTimer].c = timer->c;
+				alts[KTimer].op = CHANRCV;
+			}else{
+				timer = nil;
+				alts[KTimer].c = nil;
+				alts[KTimer].op = CHANNOP;
+			}
+			if(nbrecv(keyboardctl->c, &r) > 0)
+				goto casekeyboard;
+			flushimage(display, 1);
+			break;
+		}
+	}
+}
+
+void
+mousethread(void *v)
+{
+	Text *t, *argt;
+	int but;
+	uint q0, q1;
+	Window *w;
+	Plumbmsg *pm;
+	Mouse m;
+	char *act;
+	enum { MResize, MMouse, MPlumb, NMALT };
+	static Alt alts[NMALT+1];
+
+	USED(v);
+	threadsetname("mousethread");
+	alts[MResize].c = mousectl->resizec;
+	alts[MResize].v = nil;
+	alts[MResize].op = CHANRCV;
+	alts[MMouse].c = mousectl->c;
+	alts[MMouse].v = &mousectl->m;
+	alts[MMouse].op = CHANRCV;
+	alts[MPlumb].c = cplumb;
+	alts[MPlumb].v = &pm;
+	alts[MPlumb].op = CHANRCV;
+	if(cplumb == nil)
+		alts[MPlumb].op = CHANNOP;
+	alts[NMALT].op = CHANEND;
+	
+	for(;;){
+		switch(alt(alts)){
+		case MResize:
+			if(getwindow(display, Refnone) < 0)
+				error("attach to window");
+			draw(screen, screen->r, display->white, nil, ZP);
+			scrlresize();
+			rowresize(&row, screen->clipr);
+			flushimage(display, 1);
+			break;
+		case MPlumb:
+			if(strcmp(pm->type, "text") == 0){
+				act = plumblookup(pm->attr, "action");
+				if(act==nil || strcmp(act, "showfile")==0)
+					plumblook(pm);
+				else if(strcmp(act, "showdata")==0)
+					plumbshow(pm);
+			}
+			flushimage(display, 1);
+			plumbfree(pm);
+			break;
+		case MMouse:
+			/*
+			 * Make a copy so decisions are consistent; mousectl changes
+			 * underfoot.  Can't just receive into m because this introduces
+			 * another race; see /sys/src/libdraw/mouse.c.
+			 */
+			m = mousectl->m;
+			qlock(&row.lk);
+			t = rowwhich(&row, m.xy);
+			if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+				winlock(mousetext->w, 'M');
+				mousetext->eq0 = ~0;
+				wincommit(mousetext->w, mousetext);
+				winunlock(mousetext->w);
+			}
+			mousetext = t;
+			if(t == nil)
+				goto Continue;
+			w = t->w;
+			if(t==nil || m.buttons==0)
+				goto Continue;
+			but = 0;
+			if(m.buttons == 1)
+				but = 1;
+			else if(m.buttons == 2)
+				but = 2;
+			else if(m.buttons == 4)
+				but = 3;
+			barttext = t;
+			if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+				if(but){
+					winlock(w, 'M');
+					t->eq0 = ~0;
+					textscroll(t, but);
+					winunlock(w);
+				}
+				goto Continue;
+			}
+			if(ptinrect(m.xy, t->scrollr)){
+				if(but){
+					if(t->what == Columntag)
+						rowdragcol(&row, t->col, but);
+					else if(t->what == Tag){
+						coldragwin(t->col, t->w, but);
+						if(t->w)
+							barttext = &t->w->body;
+					}
+					if(t->col)
+						activecol = t->col;
+				}
+				goto Continue;
+			}
+			if(m.buttons){
+				if(w)
+					winlock(w, 'M');
+				t->eq0 = ~0;
+				if(w)
+					wincommit(w, t);
+				else
+					textcommit(t, TRUE);
+				if(m.buttons & 1){
+					textselect(t);
+					if(w)
+						winsettag(w);
+					argtext = t;
+					seltext = t;
+					if(t->col)
+						activecol = t->col;	/* button 1 only */
+					if(t->w!=nil && t==&t->w->body)
+						activewin = t->w;
+				}else if(m.buttons & 2){
+					if(textselect2(t, &q0, &q1, &argt))
+						execute(t, q0, q1, FALSE, argt);
+				}else if(m.buttons & 4){
+					if(textselect3(t, &q0, &q1))
+						look3(t, q0, q1, FALSE);
+				}
+				if(w)
+					winunlock(w);
+				goto Continue;
+			}
+    Continue:
+			flushimage(display, 1);
+			qunlock(&row.lk);
+			break;
+		}
+	}
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+	int	pid;
+	char	msg[ERRMAX];
+	Pid	*next;
+};
+
+void
+waitthread(void *v)
+{
+	Waitmsg *w;
+	Command *c, *lc;
+	uint pid;
+	int found, ncmd;
+	Rune *cmd;
+	char *err;
+	Text *t;
+	Pid *pids, *p, *lastp;
+	enum { WErr, WKill, WWait, WCmd, NWALT };
+	Alt alts[NWALT+1];
+
+	USED(v);
+	threadsetname("waitthread");
+	pids = nil;
+	alts[WErr].c = cerr;
+	alts[WErr].v = &err;
+	alts[WErr].op = CHANRCV;
+	alts[WKill].c = ckill;
+	alts[WKill].v = &cmd;
+	alts[WKill].op = CHANRCV;
+	alts[WWait].c = cwait;
+	alts[WWait].v = &w;
+	alts[WWait].op = CHANRCV;
+	alts[WCmd].c = ccommand;
+	alts[WCmd].v = &c;
+	alts[WCmd].op = CHANRCV;
+	alts[NWALT].op = CHANEND;
+
+	command = nil;
+	for(;;){
+		switch(alt(alts)){
+		case WErr:
+			qlock(&row.lk);
+			warning(nil, "%s", err);
+			free(err);
+			flushimage(display, 1);
+			qunlock(&row.lk);
+			break;
+		case WKill:
+			found = FALSE;
+			ncmd = runestrlen(cmd);
+			for(c=command; c; c=c->next){
+				/* -1 for blank */
+				if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+					if(postnote(PNGROUP, c->pid, "kill") < 0)
+						warning(nil, "kill %S: %r\n", cmd);
+					found = TRUE;
+				}
+			}
+			if(!found)
+				warning(nil, "Kill: no process %S\n", cmd);
+			free(cmd);
+			break;
+		case WWait:
+			pid = w->pid;
+			lc = nil;
+			for(c=command; c; c=c->next){
+				if(c->pid == pid){
+					if(lc)
+						lc->next = c->next;
+					else
+						command = c->next;
+					break;
+				}
+				lc = c;
+			}
+			qlock(&row.lk);
+			t = &row.tag;
+			textcommit(t, TRUE);
+			if(c == nil){
+				/* helper processes use this exit status */
+				if(strncmp(w->msg, "libthread", 9) != 0){
+					p = emalloc(sizeof(Pid));
+					p->pid = pid;
+					strncpy(p->msg, w->msg, sizeof(p->msg));
+					p->next = pids;
+					pids = p;
+				}
+			}else{
+				if(search(t, c->name, c->nname)){
+					textdelete(t, t->q0, t->q1, TRUE);
+					textsetselect(t, 0, 0);
+				}
+				if(w->msg[0])
+					warning(c->md, "%s: %s\n", c->name, w->msg);
+				flushimage(display, 1);
+			}
+			qunlock(&row.lk);
+			free(w);
+    Freecmd:
+			if(c){
+				if(c->iseditcmd)
+					sendul(cedit, 0);
+				free(c->text);
+				free(c->name);
+				fsysdelid(c->md);
+				free(c);
+			}
+			break;
+		case WCmd:
+			/* has this command already exited? */
+			lastp = nil;
+			for(p=pids; p!=nil; p=p->next){
+				if(p->pid == c->pid){
+					if(p->msg[0])
+						warning(c->md, "%s\n", p->msg);
+					if(lastp == nil)
+						pids = p->next;
+					else
+						lastp->next = p->next;
+					free(p);
+					goto Freecmd;
+				}
+				lastp = p;
+			}
+			c->next = command;
+			command = c;
+			qlock(&row.lk);
+			t = &row.tag;
+			textcommit(t, TRUE);
+			textinsert(t, 0, c->name, c->nname, TRUE);
+			textsetselect(t, 0, 0);
+			flushimage(display, 1);
+			qunlock(&row.lk);
+			break;
+		}
+	}
+}
+
+void
+xfidallocthread(void *v)
+{
+	Xfid *xfree, *x;
+	enum { Alloc, Free, N };
+	static Alt alts[N+1];
+
+	USED(v);
+	threadsetname("xfidallocthread");
+	alts[Alloc].c = cxfidalloc;
+	alts[Alloc].v = nil;
+	alts[Alloc].op = CHANRCV;
+	alts[Free].c = cxfidfree;
+	alts[Free].v = &x;
+	alts[Free].op = CHANRCV;
+	alts[N].op = CHANEND;
+
+	xfree = nil;
+	for(;;){
+		switch(alt(alts)){
+		case Alloc:
+			x = xfree;
+			if(x)
+				xfree = x->next;
+			else{
+				x = emalloc(sizeof(Xfid));
+				x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+				x->arg = x;
+				threadcreate(xfidctl, x->arg, STACK);
+			}
+			sendp(cxfidalloc, x);
+			break;
+		case Free:
+			x->next = xfree;
+			xfree = x;
+			break;
+		}
+	}
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void *v)
+{
+	Window *w;
+
+	USED(v);
+	threadsetname("newwindowthread");
+
+	for(;;){
+		/* only fsysproc is talking to us, so synchronization is trivial */
+		recvp(cnewwindow);
+		w = makenewwindow(nil);
+		winsettag(w);
+		sendp(cnewwindow, w);
+	}
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+	Reffont *r;
+	Font *f;
+	int i;
+
+	r = nil;
+	if(name == nil){
+		name = fontnames[fix];
+		r = reffonts[fix];
+	}
+	if(r == nil){
+		for(i=0; i<nfontcache; i++)
+			if(strcmp(name, fontcache[i]->f->name) == 0){
+				r = fontcache[i];
+				goto Found;
+			}
+		f = openfont(display, name);
+		if(f == nil){
+			warning(nil, "can't open font file %s: %r\n", name);
+			return nil;
+		}
+		r = emalloc(sizeof(Reffont));
+		r->f = f;
+		fontcache = realloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+		fontcache[nfontcache++] = r;
+	}
+    Found:
+	if(save){
+		incref(&r->ref);
+		if(reffonts[fix])
+			rfclose(reffonts[fix]);
+		reffonts[fix] = r;
+		free(fontnames[fix]);
+		fontnames[fix] = name;
+	}
+	if(setfont){
+		reffont.f = r->f;
+		incref(&r->ref);
+		rfclose(reffonts[0]);
+		font = r->f;
+		reffonts[0] = r;
+		incref(&r->ref);
+		iconinit();
+	}
+	incref(&r->ref);
+	return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+	int i;
+
+	if(decref(&r->ref) == 0){
+		for(i=0; i<nfontcache; i++)
+			if(r == fontcache[i])
+				break;
+		if(i >= nfontcache)
+			warning(nil, "internal error: can't find font in cache\n");
+		else{
+			nfontcache--;
+			memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+		}
+		freefont(r->f);
+		free(r);
+	}
+}
+
+Cursor boxcursor = {
+	{-7, -7},
+	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+	 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+	 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+	Rectangle r;
+	Image *tmp;
+
+	/* Blue */
+	tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+	tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+	tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+	tagcols[TEXT] = display->black;
+	tagcols[HTEXT] = display->black;
+
+	/* Yellow */
+	textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+	textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+	textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+	textcols[TEXT] = display->black;
+	textcols[HTEXT] = display->black;
+
+	if(button){
+		freeimage(button);
+		freeimage(modbutton);
+		freeimage(colbutton);
+	}
+
+	r = Rect(0, 0, Scrollwid+2, font->height+1);
+	button = allocimage(display, r, screen->chan, 0, DNofill);
+	draw(button, r, tagcols[BACK], nil, r.min);
+	r.max.x -= 2;
+	border(button, r, 2, tagcols[BORD], ZP);
+
+	r = button->r;
+	modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+	draw(modbutton, r, tagcols[BACK], nil, r.min);
+	r.max.x -= 2;
+	border(modbutton, r, 2, tagcols[BORD], ZP);
+	r = insetrect(r, 2);
+	tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+	draw(modbutton, r, tmp, nil, ZP);
+	freeimage(tmp);
+
+	r = button->r;
+	colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+	but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+	but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+acmeputsnarf(void)
+{
+	int fd, i, n;
+
+	if(snarffd<0 || snarfbuf.nc==0)
+		return;
+	if(snarfbuf.nc > MAXSNARF)
+		return;
+	fd = open("/dev/snarf", OWRITE);
+	if(fd < 0)
+		return;
+	for(i=0; i<snarfbuf.nc; i+=n){
+		n = snarfbuf.nc-i;
+		if(n >= NSnarf)
+			n = NSnarf;
+		bufread(&snarfbuf, i, snarfrune, n);
+		if(fprint(fd, "%.*S", n, snarfrune) < 0)
+			break;
+	}
+	close(fd);
+}
+
+void
+acmegetsnarf()
+{
+	int nulls;
+
+	if(snarfbuf.nc > MAXSNARF)
+		return;
+	if(snarffd < 0)
+		return;
+	seek(snarffd, 0, 0);
+	bufreset(&snarfbuf);
+	bufload(&snarfbuf, 0, snarffd, &nulls);
+}
blob - /dev/null
blob + d64db61b6d24c6afa9590104461a271c48942b95 (mode 644)
--- /dev/null
+++ src/cmd/acme/addr.c
@@ -0,0 +1,269 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	None = 0,
+	Fore = '+',
+	Back = '-',
+};
+
+enum
+{
+	Char,
+	Line,
+};
+
+int
+isaddrc(int r)
+{
+	if(r && utfrune("0123456789+-/$.#,;", r)!=nil)
+		return TRUE;
+	return FALSE;
+}
+
+/*
+ * quite hard: could be almost anything but white space, but we are a little conservative,
+ * aiming for regular expressions of alphanumerics and no white space
+ */
+int
+isregexc(int r)
+{
+	if(r == 0)
+		return FALSE;
+	if(isalnum(r))
+		return TRUE;
+	if(utfrune("^+-.*?#,;[]()$", r)!=nil)
+		return TRUE;
+	return FALSE;
+}
+
+Range
+number(Mntdir *md, Text *t, Range r, int line, int dir, int size, int *evalp)
+{
+	uint q0, q1;
+
+	if(size == Char){
+		if(dir == Fore)
+			line = r.q1+line;
+		else if(dir == Back){
+			if(r.q0==0 && line>0)
+				r.q0 = t->file->b.nc;
+			line = r.q0 - line;
+		}
+		if(line<0 || line>t->file->b.nc)
+			goto Rescue;
+		*evalp = TRUE;
+		return (Range){line, line};
+	}
+	q0 = r.q0;
+	q1 = r.q1;
+	switch(dir){
+	case None:
+		q0 = 0;
+		q1 = 0;
+	Forward:
+		while(line>0 && q1<t->file->b.nc)
+			if(textreadc(t, q1++) == '\n' || q1==t->file->b.nc)
+				if(--line > 0)
+					q0 = q1;
+		if(line > 0)
+			goto Rescue;
+		break;
+	case Fore:
+		if(q1 > 0)
+			while(textreadc(t, q1-1) != '\n')
+				q1++;
+		q0 = q1;
+		goto Forward;
+	case Back:
+		if(q0 < t->file->b.nc)
+			while(q0>0 && textreadc(t, q0-1)!='\n')
+				q0--;
+		q1 = q0;
+		while(line>0 && q0>0){
+			if(textreadc(t, q0-1) == '\n'){
+				if(--line >= 0)
+					q1 = q0;
+			}
+			--q0;
+		}
+		if(line > 0)
+			goto Rescue;
+		while(q0>0 && textreadc(t, q0-1)!='\n')
+			--q0;
+	}
+	*evalp = TRUE;
+	return (Range){q0, q1};
+
+    Rescue:
+	if(md != nil)
+		warning(nil, "address out of range\n");
+	*evalp = FALSE;
+	return r;
+}
+
+
+Range
+regexp(Mntdir *md, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp)
+{
+	int found;
+	Rangeset sel;
+	int q;
+
+	if(pat[0] == '\0' && rxnull()){
+		warning(md, "no previous regular expression\n");
+		*foundp = FALSE;
+		return r;
+	}
+	if(pat[0] && rxcompile(pat) == FALSE){
+		*foundp = FALSE;
+		return r;
+	}
+	if(dir == Back)
+		found = rxbexecute(t, r.q0, &sel);
+	else{
+		if(lim.q0 < 0)
+			q = Infinity;
+		else
+			q = lim.q1;
+		found = rxexecute(t, nil, r.q1, q, &sel);
+	}
+	if(!found && md==nil)
+		warning(nil, "no match for regexp\n");
+	*foundp = found;
+	return sel.r[0];
+}
+
+Range
+address(Mntdir *md, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint),  int *evalp, uint *qp)
+{
+	int dir, size, npat;
+	int prevc, c, nc, n;
+	uint q;
+	Rune *pat;
+	Range r, nr;
+
+	r = ar;
+	q = q0;
+	dir = None;
+	size = Line;
+	c = 0;
+	while(q < q1){
+		prevc = c;
+		c = (*getc)(a, q++);
+		switch(c){
+		default:
+			*qp = q-1;
+			return r;
+		case ';':
+			ar = r;
+			/* fall through */
+		case ',':
+			if(prevc == 0)	/* lhs defaults to 0 */
+				r.q0 = 0;
+			if(q>=q1 && t!=nil && t->file!=nil)	/* rhs defaults to $ */
+				r.q1 = t->file->b.nc;
+			else{
+				nr = address(md, t, lim, ar, a, q, q1, getc, evalp, &q);
+				r.q1 = nr.q1;
+			}
+			*qp = q;
+			return r;
+		case '+':
+		case '-':
+			if(*evalp && (prevc=='+' || prevc=='-'))
+				if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?')
+					r = number(md, t, r, 1, prevc, Line, evalp);	/* do previous one */
+			dir = c;
+			break;
+		case '.':
+		case '$':
+			if(q != q0+1){
+				*qp = q-1;
+				return r;
+			}
+			if(*evalp)
+				if(c == '.')
+					r = ar;
+				else
+					r = (Range){t->file->b.nc, t->file->b.nc};
+			if(q < q1)
+				dir = Fore;
+			else
+				dir = None;
+			break;
+		case '#':
+			if(q==q1 || (c=(*getc)(a, q++))<'0' || '9'<c){
+				*qp = q-1;
+				return r;
+			}
+			size = Char;
+			/* fall through */
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+			n = c -'0';
+			while(q<q1){
+				c = (*getc)(a, q++);
+				if(c<'0' || '9'<c){
+					q--;
+					break;
+				}
+				n = n*10+(c-'0');
+			}
+			if(*evalp)
+				r = number(md, t, r, n, dir, size, evalp);
+			dir = None;
+			size = Line;
+			break;
+		case '?':
+			dir = Back;
+			/* fall through */
+		case '/':
+			npat = 0;
+			pat = nil;
+			while(q<q1){
+				c = (*getc)(a, q++);
+				switch(c){
+				case '\n':
+					--q;
+					goto out;
+				case '\\':
+					pat = runerealloc(pat, npat+1);
+					pat[npat++] = c;
+					if(q == q1)
+						goto out;
+					c = (*getc)(a, q++);
+					break;
+				case '/':
+					goto out;
+				}
+				pat = runerealloc(pat, npat+1);
+				pat[npat++] = c;
+			}
+		    out:
+			pat = runerealloc(pat, npat+1);
+			pat[npat] = 0;
+			if(*evalp)
+				r = regexp(md, t, lim, r, pat, dir, evalp);
+			free(pat);
+			dir = None;
+			size = Line;
+			break;
+		}
+	}
+	if(*evalp && dir != None)
+		r = number(md, t, r, 1, dir, Line, evalp);	/* do previous one */
+	*qp = q;
+	return r;
+}
blob - /dev/null
blob + 39982f17e73e1dac06c371a6a9bc02b019a50221 (mode 644)
--- /dev/null
+++ src/cmd/acme/buff.c
@@ -0,0 +1,322 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	Slop = 100,	/* room to grow with reallocation */
+};
+
+static
+void
+sizecache(Buffer *b, uint n)
+{
+	if(n <= b->cmax)
+		return;
+	b->cmax = n+Slop;
+	b->c = runerealloc(b->c, b->cmax);
+}
+
+static
+void
+addblock(Buffer *b, uint i, uint n)
+{
+	if(i > b->nbl)
+		error("internal error: addblock");
+
+	b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
+	if(i < b->nbl)
+		memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
+	b->bl[i] = disknewblock(disk, n);
+	b->nbl++;
+}
+
+static
+void
+delblock(Buffer *b, uint i)
+{
+	if(i >= b->nbl)
+		error("internal error: delblock");
+
+	diskrelease(disk, b->bl[i]);
+	b->nbl--;
+	if(i < b->nbl)
+		memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
+	b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
+}
+
+/*
+ * Move cache so b->cq <= q0 < b->cq+b->cnc.
+ * If at very end, q0 will fall on end of cache block.
+ */
+
+static
+void
+flush(Buffer *b)
+{
+	if(b->cdirty || b->cnc==0){
+		if(b->cnc == 0)
+			delblock(b, b->cbi);
+		else
+			diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
+		b->cdirty = FALSE;
+	}
+}
+
+static
+void
+setcache(Buffer *b, uint q0)
+{
+	Block **blp, *bl;
+	uint i, q;
+
+	if(q0 > b->nc)
+		error("internal error: setcache");
+	/*
+	 * flush and reload if q0 is not in cache.
+	 */
+	if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
+		return;
+	/*
+	 * if q0 is at end of file and end of cache, continue to grow this block
+	 */
+	if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<Maxblock)
+		return;
+	flush(b);
+	/* find block */
+	if(q0 < b->cq){
+		q = 0;
+		i = 0;
+	}else{
+		q = b->cq;
+		i = b->cbi;
+	}
+	blp = &b->bl[i];
+	while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){
+		q += (*blp)->u.n;
+		i++;
+		blp++;
+		if(i >= b->nbl)
+			error("block not found");
+	}
+	bl = *blp;
+	/* remember position */
+	b->cbi = i;
+	b->cq = q;
+	sizecache(b, bl->u.n);
+	b->cnc = bl->u.n;
+	/*read block*/
+	diskread(disk, bl, b->c, b->cnc);
+}
+
+void
+bufinsert(Buffer *b, uint q0, Rune *s, uint n)
+{
+	uint i, m, t, off;
+
+	if(q0 > b->nc)
+		error("internal error: bufinsert");
+
+	while(n > 0){
+		setcache(b, q0);
+		off = q0-b->cq;
+		if(b->cnc+n <= Maxblock){
+			/* Everything fits in one block. */
+			t = b->cnc+n;
+			m = n;
+			if(b->bl == nil){	/* allocate */
+				if(b->cnc != 0)
+					error("internal error: bufinsert1 cnc!=0");
+				addblock(b, 0, t);
+				b->cbi = 0;
+			}
+			sizecache(b, t);
+			runemove(b->c+off+m, b->c+off, b->cnc-off);
+			runemove(b->c+off, s, m);
+			b->cnc = t;
+			goto Tail;
+		}
+		/*
+		 * We must make a new block.  If q0 is at
+		 * the very beginning or end of this block,
+		 * just make a new block and fill it.
+		 */
+		if(q0==b->cq || q0==b->cq+b->cnc){
+			if(b->cdirty)
+				flush(b);
+			m = min(n, Maxblock);
+			if(b->bl == nil){	/* allocate */
+				if(b->cnc != 0)
+					error("internal error: bufinsert2 cnc!=0");
+				i = 0;
+			}else{
+				i = b->cbi;
+				if(q0 > b->cq)
+					i++;
+			}
+			addblock(b, i, m);
+			sizecache(b, m);
+			runemove(b->c, s, m);
+			b->cq = q0;
+			b->cbi = i;
+			b->cnc = m;
+			goto Tail;
+		}
+		/*
+		 * Split the block; cut off the right side and
+		 * let go of it.
+		 */
+		m = b->cnc-off;
+		if(m > 0){
+			i = b->cbi+1;
+			addblock(b, i, m);
+			diskwrite(disk, &b->bl[i], b->c+off, m);
+			b->cnc -= m;
+		}
+		/*
+		 * Now at end of block.  Take as much input
+		 * as possible and tack it on end of block.
+		 */
+		m = min(n, Maxblock-b->cnc);
+		sizecache(b, b->cnc+m);
+		runemove(b->c+b->cnc, s, m);
+		b->cnc += m;
+  Tail:
+		b->nc += m;
+		q0 += m;
+		s += m;
+		n -= m;
+		b->cdirty = TRUE;
+	}
+}
+
+void
+bufdelete(Buffer *b, uint q0, uint q1)
+{
+	uint m, n, off;
+
+	if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
+		error("internal error: bufdelete");
+	while(q1 > q0){
+		setcache(b, q0);
+		off = q0-b->cq;
+		if(q1 > b->cq+b->cnc)
+			n = b->cnc - off;
+		else
+			n = q1-q0;
+		m = b->cnc - (off+n);
+		if(m > 0)
+			runemove(b->c+off, b->c+off+n, m);
+		b->cnc -= n;
+		b->cdirty = TRUE;
+		q1 -= n;
+		b->nc -= n;
+	}
+}
+
+static int
+bufloader(void *v, uint q0, Rune *r, int nr)
+{
+	bufinsert(v, q0, r, nr);
+	return nr;
+}
+
+uint
+loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg)
+{
+	char *p;
+	Rune *r;
+	int l, m, n, nb, nr;
+	uint q1;
+
+	p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]);
+	r = runemalloc(Maxblock);
+	m = 0;
+	n = 1;
+	q1 = q0;
+	/*
+	 * At top of loop, may have m bytes left over from
+	 * last pass, possibly representing a partial rune.
+	 */
+	while(n > 0){
+		n = read(fd, p+m, Maxblock);
+		if(n < 0){
+			warning(nil, "read error in Buffer.load");
+			break;
+		}
+		m += n;
+		p[m] = 0;
+		l = m;
+		if(n > 0)
+			l -= UTFmax;
+		cvttorunes(p, l, r, &nb, &nr, nulls);
+		memmove(p, p+nb, m-nb);
+		m -= nb;
+		q1 += (*f)(arg, q1, r, nr);
+	}
+	free(p);
+	free(r);
+	return q1-q0;
+}
+
+uint
+bufload(Buffer *b, uint q0, int fd, int *nulls)
+{
+	if(q0 > b->nc)
+		error("internal error: bufload");
+	return loadfile(fd, q0, nulls, bufloader, b);
+}
+
+void
+bufread(Buffer *b, uint q0, Rune *s, uint n)
+{
+	uint m;
+
+	if(!(q0<=b->nc && q0+n<=b->nc))
+		error("bufread: internal error");
+
+	while(n > 0){
+		setcache(b, q0);
+		m = min(n, b->cnc-(q0-b->cq));
+		runemove(s, b->c+(q0-b->cq), m);
+		q0 += m;
+		s += m;
+		n -= m;
+	}
+}
+
+void
+bufreset(Buffer *b)
+{
+	int i;
+
+	b->nc = 0;
+	b->cnc = 0;
+	b->cq = 0;
+	b->cdirty = 0;
+	b->cbi = 0;
+	/* delete backwards to avoid n² behavior */
+	for(i=b->nbl-1; --i>=0; )
+		delblock(b, i);
+}
+
+void
+bufclose(Buffer *b)
+{
+	bufreset(b);
+	free(b->c);
+	b->c = nil;
+	b->cnc = 0;
+	free(b->bl);
+	b->bl = nil;
+	b->nbl = 0;
+}
blob - /dev/null
blob + 0e6ff4092e1c908f557eef8ba6702e09f12ab07a (mode 644)
--- /dev/null
+++ src/cmd/acme/cols.c
@@ -0,0 +1,556 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Rune Lheader[] = {
+	'N', 'e', 'w', ' ',
+	'C', 'u', 't', ' ',
+	'P', 'a', 's', 't', 'e', ' ',
+	'S', 'n', 'a', 'r', 'f', ' ',
+	'S', 'o', 'r', 't', ' ',
+	'Z', 'e', 'r', 'o', 'x', ' ',
+	'D', 'e', 'l', 'c', 'o', 'l', ' ',
+	0
+};
+
+void
+colinit(Column *c, Rectangle r)
+{
+	Rectangle r1;
+	Text *t;
+
+	draw(screen, r, display->white, nil, ZP);
+	c->r = r;
+	c->w = nil;
+	c->nw = 0;
+	t = &c->tag;
+	t->w = nil;
+	t->col = c;
+	r1 = r;
+	r1.max.y = r1.min.y + font->height;
+	textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
+	t->what = Columntag;
+	r1.min.y = r1.max.y;
+	r1.max.y += Border;
+	draw(screen, r1, display->black, nil, ZP);
+	textinsert(t, 0, Lheader, 38, TRUE);
+	textsetselect(t, t->file->b.nc, t->file->b.nc);
+	draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
+	c->safe = TRUE;
+}
+
+Window*
+coladd(Column *c, Window *w, Window *clone, int y)
+{
+	Rectangle r, r1;
+	Window *v;
+	int i, t;
+
+	v = nil;
+	r = c->r;
+	r.min.y = c->tag.fr.r.max.y+Border;
+	if(y<r.min.y && c->nw>0){	/* steal half of last window by default */
+		v = c->w[c->nw-1];
+		y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
+	}
+	/* look for window we'll land on */
+	for(i=0; i<c->nw; i++){
+		v = c->w[i];
+		if(y < v->r.max.y)
+			break;
+	}
+	if(c->nw > 0){
+		if(i < c->nw)
+			i++;	/* new window will go after v */
+		/*
+		 * if v's too small, grow it first.
+		 */
+		if(!c->safe || v->body.fr.maxlines<=3){
+			colgrow(c, v, 1);
+			y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
+		}
+		r = v->r;
+		if(i == c->nw)
+			t = c->r.max.y;
+		else
+			t = c->w[i]->r.min.y-Border;
+		r.max.y = t;
+		draw(screen, r, textcols[BACK], nil, ZP);
+		r1 = r;
+		y = min(y, t-(v->tag.fr.font->height+v->body.fr.font->height+Border+1));
+		r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height);
+		r1.min.y = winresize(v, r1, FALSE);
+		r1.max.y = r1.min.y+Border;
+		draw(screen, r1, display->black, nil, ZP);
+		r.min.y = r1.max.y;
+	}
+	if(w == nil){
+		w = emalloc(sizeof(Window));
+		w->col = c;
+		draw(screen, r, textcols[BACK], nil, ZP);
+		wininit(w, clone, r);
+	}else{
+		w->col = c;
+		winresize(w, r, FALSE);
+	}
+	w->tag.col = c;
+	w->tag.row = c->row;
+	w->body.col = c;
+	w->body.row = c->row;
+	c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
+	memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
+	c->nw++;
+	c->w[i] = w;
+	savemouse(w);
+	/* near but not on the button */
+	moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
+	barttext = &w->body;
+	c->safe = TRUE;
+	return w;
+}
+
+void
+colclose(Column *c, Window *w, int dofree)
+{
+	Rectangle r;
+	int i;
+
+	/* w is locked */
+	if(!c->safe)
+		colgrow(c, w, 1);
+	for(i=0; i<c->nw; i++)
+		if(c->w[i] == w)
+			goto Found;
+	error("can't find window");
+  Found:
+	r = w->r;
+	w->tag.col = nil;
+	w->body.col = nil;
+	w->col = nil;
+	restoremouse(w);
+	if(dofree){
+		windelete(w);
+		winclose(w);
+	}
+	memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
+	c->nw--;
+	c->w = realloc(c->w, c->nw*sizeof(Window*));
+	if(c->nw == 0){
+		draw(screen, r, display->white, nil, ZP);
+		return;
+	}
+	if(i == c->nw){		/* extend last window down */
+		w = c->w[i-1];
+		r.min.y = w->r.min.y;
+		r.max.y = c->r.max.y;
+	}else{			/* extend next window up */
+		w = c->w[i];
+		r.max.y = w->r.max.y;
+	}
+	draw(screen, r, textcols[BACK], nil, ZP);
+	if(c->safe)
+		winresize(w, r, FALSE);
+}
+
+void
+colcloseall(Column *c)
+{
+	int i;
+	Window *w;
+
+	if(c == activecol)
+		activecol = nil;
+	textclose(&c->tag);
+	for(i=0; i<c->nw; i++){
+		w = c->w[i];
+		winclose(w);
+	}
+	c->nw = 0;
+	free(c->w);
+	free(c);
+	clearmouse();
+}
+
+void
+colmousebut(Column *c)
+{
+	moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
+}
+
+void
+colresize(Column *c, Rectangle r)
+{
+	int i;
+	Rectangle r1, r2;
+	Window *w;
+
+	clearmouse();
+	r1 = r;
+	r1.max.y = r1.min.y + c->tag.fr.font->height;
+	textresize(&c->tag, r1);
+	draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
+	r1.min.y = r1.max.y;
+	r1.max.y += Border;
+	draw(screen, r1, display->black, nil, ZP);
+	r1.max.y = r.max.y;
+	for(i=0; i<c->nw; i++){
+		w = c->w[i];
+		w->maxlines = 0;
+		if(i == c->nw-1)
+			r1.max.y = r.max.y;
+		else
+			r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r);
+		r2 = r1;
+		r2.max.y = r2.min.y+Border;
+		draw(screen, r2, display->black, nil, ZP);
+		r1.min.y = r2.max.y;
+		r1.min.y = winresize(w, r1, FALSE);
+	}
+	c->r = r;
+}
+
+static
+int
+colcmp(const void *a, const void *b)
+{
+	Rune *r1, *r2;
+	int i, nr1, nr2;
+
+	r1 = (*(Window**)a)->body.file->name;
+	nr1 = (*(Window**)a)->body.file->nname;
+	r2 = (*(Window**)b)->body.file->name;
+	nr2 = (*(Window**)b)->body.file->nname;
+	for(i=0; i<nr1 && i<nr2; i++){
+		if(*r1 != *r2)
+			return *r1-*r2;
+		r1++;
+		r2++;
+	}
+	return nr1-nr2;
+}
+
+void
+colsort(Column *c)
+{
+	int i, y;
+	Rectangle r, r1, *rp;
+	Window **wp, *w;
+
+	if(c->nw == 0)
+		return;
+	clearmouse();
+	rp = emalloc(c->nw*sizeof(Rectangle));
+	wp = emalloc(c->nw*sizeof(Window*));
+	memmove(wp, c->w, c->nw*sizeof(Window*));
+	qsort(wp, c->nw, sizeof(Window*), colcmp);
+	for(i=0; i<c->nw; i++)
+		rp[i] = wp[i]->r;
+	r = c->r;
+	r.min.y = c->tag.fr.r.max.y;
+	draw(screen, r, textcols[BACK], nil, ZP);
+	y = r.min.y;
+	for(i=0; i<c->nw; i++){
+		w = wp[i];
+		r.min.y = y;
+		if(i == c->nw-1)
+			r.max.y = c->r.max.y;
+		else
+			r.max.y = r.min.y+Dy(w->r)+Border;
+		r1 = r;
+		r1.max.y = r1.min.y+Border;
+		draw(screen, r1, display->black, nil, ZP);
+		r.min.y = r1.max.y;
+		y = winresize(w, r, FALSE);
+	}
+	free(rp);
+	free(c->w);
+	c->w = wp;
+}
+
+void
+colgrow(Column *c, Window *w, int but)
+{
+	Rectangle r, cr;
+	int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
+	Window *v;
+
+	for(i=0; i<c->nw; i++)
+		if(c->w[i] == w)
+			goto Found;
+	error("can't find window");
+
+  Found:
+	cr = c->r;
+	if(but < 0){	/* make sure window fills its own space properly */
+		r = w->r;
+		if(i==c->nw-1 || c->safe==FALSE)
+			r.max.y = cr.max.y;
+		else
+			r.max.y = c->w[i+1]->r.min.y;
+		winresize(w, r, FALSE);
+		return;
+	}
+	cr.min.y = c->w[0]->r.min.y;
+	if(but == 3){	/* full size */
+		if(i != 0){
+			v = c->w[0];
+			c->w[0] = w;
+			c->w[i] = v;
+		}
+		draw(screen, cr, textcols[BACK], nil, ZP);
+		winresize(w, cr, FALSE);
+		for(i=1; i<c->nw; i++)
+			c->w[i]->body.fr.maxlines = 0;
+		c->safe = FALSE;
+		return;
+	}
+	/* store old #lines for each window */
+	onl = w->body.fr.maxlines;
+	nl = emalloc(c->nw * sizeof(int));
+	ny = emalloc(c->nw * sizeof(int));
+	tot = 0;
+	for(j=0; j<c->nw; j++){
+		l = c->w[j]->body.fr.maxlines;
+		nl[j] = l;
+		tot += l;
+	}
+	/* approximate new #lines for this window */
+	if(but == 2){	/* as big as can be */
+		memset(nl, 0, c->nw * sizeof(int));
+		goto Pack;
+	}
+	nnl = min(onl + max(min(5, w->maxlines), onl/2), tot);
+	if(nnl < w->maxlines)
+		nnl = (w->maxlines+nnl)/2;
+	if(nnl == 0)
+		nnl = 2;
+	dnl = nnl - onl;
+	/* compute new #lines for each window */
+	for(k=1; k<c->nw; k++){
+		/* prune from later window */
+		j = i+k;
+		if(j<c->nw && nl[j]){
+			l = min(dnl, max(1, nl[j]/2));
+			nl[j] -= l;
+			nl[i] += l;
+			dnl -= l;
+		}
+		/* prune from earlier window */
+		j = i-k;
+		if(j>=0 && nl[j]){
+			l = min(dnl, max(1, nl[j]/2));
+			nl[j] -= l;
+			nl[i] += l;
+			dnl -= l;
+		}
+	}
+    Pack:
+	/* pack everyone above */
+	y1 = cr.min.y;
+	for(j=0; j<i; j++){
+		v = c->w[j];
+		r = v->r;
+		r.min.y = y1;
+		r.max.y = y1+Dy(v->tag.all);
+		if(nl[j])
+			r.max.y += 1 + nl[j]*v->body.fr.font->height;
+		if(!c->safe || !eqrect(v->r, r)){
+			draw(screen, r, textcols[BACK], nil, ZP);
+			winresize(v, r, c->safe);
+		}
+		r.min.y = v->r.max.y;
+		r.max.y += Border;
+		draw(screen, r, display->black, nil, ZP);
+		y1 = r.max.y;
+	}
+	/* scan to see new size of everyone below */
+	y2 = c->r.max.y;
+	for(j=c->nw-1; j>i; j--){
+		v = c->w[j];
+		r = v->r;
+		r.min.y = y2-Dy(v->tag.all);
+		if(nl[j])
+			r.min.y -= 1 + nl[j]*v->body.fr.font->height;
+		r.min.y -= Border;
+		ny[j] = r.min.y;
+		y2 = r.min.y;
+	}
+	/* compute new size of window */
+	r = w->r;
+	r.min.y = y1;
+	r.max.y = r.min.y+Dy(w->tag.all);
+	h = w->body.fr.font->height;
+	if(y2-r.max.y >= 1+h+Border){
+		r.max.y += 1;
+		r.max.y += h*((y2-r.max.y)/h);
+	}
+	/* draw window */
+	if(!c->safe || !eqrect(w->r, r)){
+		draw(screen, r, textcols[BACK], nil, ZP);
+		winresize(w, r, c->safe);
+	}
+	if(i < c->nw-1){
+		r.min.y = r.max.y;
+		r.max.y += Border;
+		draw(screen, r, display->black, nil, ZP);
+		for(j=i+1; j<c->nw; j++)
+			ny[j] -= (y2-r.max.y);
+	}
+	/* pack everyone below */
+	y1 = r.max.y;
+	for(j=i+1; j<c->nw; j++){
+		v = c->w[j];
+		r = v->r;
+		r.min.y = y1;
+		r.max.y = y1+Dy(v->tag.all);
+		if(nl[j])
+			r.max.y += 1 + nl[j]*v->body.fr.font->height;
+		if(!c->safe || !eqrect(v->r, r)){
+			draw(screen, r, textcols[BACK], nil, ZP);
+			winresize(v, r, c->safe);
+		}
+		if(j < c->nw-1){	/* no border on last window */
+			r.min.y = v->r.max.y;
+			r.max.y += Border;
+			draw(screen, r, display->black, nil, ZP);
+		}
+		y1 = r.max.y;
+	}
+	r = w->r;
+	r.min.y = y1;
+	r.max.y = c->r.max.y;
+	draw(screen, r, textcols[BACK], nil, ZP);
+	free(nl);
+	free(ny);
+	c->safe = TRUE;
+	winmousebut(w);
+}
+
+void
+coldragwin(Column *c, Window *w, int but)
+{
+	Rectangle r;
+	int i, b;
+	Point p, op;
+	Window *v;
+	Column *nc;
+
+	clearmouse();
+	setcursor(mousectl, &boxcursor);
+	b = mouse->buttons;
+	op = mouse->xy;
+	while(mouse->buttons == b)
+		readmouse(mousectl);
+	setcursor(mousectl, nil);
+	if(mouse->buttons){
+		while(mouse->buttons)
+			readmouse(mousectl);
+		return;
+	}
+
+	for(i=0; i<c->nw; i++)
+		if(c->w[i] == w)
+			goto Found;
+	error("can't find window");
+
+  Found:
+	p = mouse->xy;
+	if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
+		colgrow(c, w, but);
+		winmousebut(w);
+		return;
+	}
+	/* is it a flick to the right? */
+	if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
+		p.x += Dx(w->r);	/* yes: toss to next column */
+	nc = rowwhichcol(c->row, p);
+	if(nc!=nil && nc!=c){
+		colclose(c, w, FALSE);
+		coladd(nc, w, nil, p.y);
+		winmousebut(w);
+		return;
+	}
+	if(i==0 && c->nw==1)
+		return;			/* can't do it */
+	if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
+	|| (i==0 && p.y>w->r.max.y)){
+		/* shuffle */
+		colclose(c, w, FALSE);
+		coladd(c, w, nil, p.y);
+		winmousebut(w);
+		return;
+	}
+	if(i == 0)
+		return;
+	v = c->w[i-1];
+	if(p.y < v->tag.all.max.y)
+		p.y = v->tag.all.max.y;
+	if(p.y > w->r.max.y-Dy(w->tag.all)-Border)
+		p.y = w->r.max.y-Dy(w->tag.all)-Border;
+	r = v->r;
+	r.max.y = p.y;
+	if(r.max.y > v->body.fr.r.min.y){
+		r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height;
+		if(v->body.fr.r.min.y == v->body.fr.r.max.y)
+			r.max.y++;
+	}
+	if(!eqrect(v->r, r)){
+		draw(screen, r, textcols[BACK], nil, ZP);
+		winresize(v, r, c->safe);
+	}
+	r.min.y = v->r.max.y;
+	r.max.y = r.min.y+Border;
+	draw(screen, r, display->black, nil, ZP);
+	r.min.y = r.max.y;
+	if(i == c->nw-1)
+		r.max.y = c->r.max.y;
+	else
+		r.max.y = c->w[i+1]->r.min.y-Border;
+	if(!eqrect(w->r, r)){
+		draw(screen, r, textcols[BACK], nil, ZP);
+		winresize(w, r, c->safe);
+	}
+	c->safe = TRUE;
+    	winmousebut(w);
+}
+
+Text*
+colwhich(Column *c, Point p)
+{
+	int i;
+	Window *w;
+
+	if(!ptinrect(p, c->r))
+		return nil;
+	if(ptinrect(p, c->tag.all))
+		return &c->tag;
+	for(i=0; i<c->nw; i++){
+		w = c->w[i];
+		if(ptinrect(p, w->r)){
+			if(ptinrect(p, w->tag.all))
+				return &w->tag;
+			return &w->body;
+		}
+	}
+	return nil;
+}
+
+int
+colclean(Column *c)
+{
+	int i, clean;
+
+	clean = TRUE;
+	for(i=0; i<c->nw; i++)
+		clean &= winclean(c->w[i], TRUE);
+	return clean;
+}
blob - /dev/null
blob + b2a443dacb90e5b638013a7af0dbcb8736a711fe (mode 644)
--- /dev/null
+++ src/cmd/acme/dat.h
@@ -0,0 +1,546 @@
+enum
+{
+	Qdir,
+	Qacme,
+	Qcons,
+	Qconsctl,
+	Qdraw,
+	Qeditout,
+	Qindex,
+	Qlabel,
+	Qnew,
+
+	QWaddr,
+	QWbody,
+	QWctl,
+	QWdata,
+	QWeditout,
+	QWevent,
+	QWrdsel,
+	QWwrsel,
+	QWtag,
+	QMAX,
+};
+
+enum
+{
+	Blockincr =	256,
+	Maxblock = 	8*1024,
+	NRange =		10,
+	Infinity = 		0x7FFFFFFF,	/* huge value for regexp address */
+};
+
+typedef	struct	Block Block;
+typedef	struct	Buffer Buffer;
+typedef	struct	Command Command;
+typedef	struct	Column Column;
+typedef	struct	Dirlist Dirlist;
+typedef	struct	Dirtab Dirtab;
+typedef	struct	Disk Disk;
+typedef	struct	Expand Expand;
+typedef	struct	Fid Fid;
+typedef	struct	File File;
+typedef	struct	Elog Elog;
+typedef	struct	Mntdir Mntdir;
+typedef	struct	Range Range;
+typedef	struct	Rangeset Rangeset;
+typedef	struct	Reffont Reffont;
+typedef	struct	Row Row;
+typedef	struct	Runestr Runestr;
+typedef	struct	Text Text;
+typedef	struct	Timer Timer;
+typedef	struct	Window Window;
+typedef	struct	Xfid Xfid;
+
+struct Runestr
+{
+	Rune	*r;
+	int	nr;
+};
+
+struct Range
+{
+	int	q0;
+	int	q1;
+};
+
+struct Block
+{
+	uint		addr;	/* disk address in bytes */
+	union
+	{
+		uint	n;		/* number of used runes in block */
+		Block	*next;	/* pointer to next in free list */
+	} u;
+};
+
+struct Disk
+{
+	int		fd;
+	uint		addr;	/* length of temp file */
+	Block	*free[Maxblock/Blockincr+1];
+};
+
+Disk*	diskinit(void);
+Block*	disknewblock(Disk*, uint);
+void		diskrelease(Disk*, Block*);
+void		diskread(Disk*, Block*, Rune*, uint);
+void		diskwrite(Disk*, Block**, Rune*, uint);
+
+struct Buffer
+{
+	uint	nc;
+	Rune	*c;			/* cache */
+	uint	cnc;			/* bytes in cache */
+	uint	cmax;		/* size of allocated cache */
+	uint	cq;			/* position of cache */
+	int		cdirty;	/* cache needs to be written */
+	uint	cbi;			/* index of cache Block */
+	Block	**bl;		/* array of blocks */
+	uint	nbl;			/* number of blocks */
+};
+void		bufinsert(Buffer*, uint, Rune*, uint);
+void		bufdelete(Buffer*, uint, uint);
+uint		bufload(Buffer*, uint, int, int*);
+void		bufread(Buffer*, uint, Rune*, uint);
+void		bufclose(Buffer*);
+void		bufreset(Buffer*);
+
+struct Elog
+{
+	short	type;		/* Delete, Insert, Filename */
+	uint		q0;		/* location of change (unused in f) */
+	uint		nd;		/* number of deleted characters */
+	uint		nr;		/* # runes in string or file name */
+	Rune		*r;
+};
+void	elogterm(File*);
+void	elogclose(File*);
+void	eloginsert(File*, int, Rune*, int);
+void	elogdelete(File*, int, int);
+void	elogreplace(File*, int, int, Rune*, int);
+void	elogapply(File*);
+
+struct File
+{
+	Buffer	b;			/* the data */
+	Buffer	delta;	/* transcript of changes */
+	Buffer	epsilon;	/* inversion of delta for redo */
+	Buffer	*elogbuf;	/* log of pending editor changes */
+	Elog		elog;		/* current pending change */
+	Rune		*name;	/* name of associated file */
+	int		nname;	/* size of name */
+	uvlong	qidpath;	/* of file when read */
+	uint		mtime;	/* of file when read */
+	int		dev;		/* of file when read */
+	int		unread;	/* file has not been read from disk */
+	int		editclean;	/* mark clean after edit command */
+
+	int		seq;		/* if seq==0, File acts like Buffer */
+	int		mod;
+	Text		*curtext;	/* most recently used associated text */
+	Text		**text;	/* list of associated texts */
+	int		ntext;
+	int		dumpid;	/* used in dumping zeroxed windows */
+};
+File*		fileaddtext(File*, Text*);
+void		fileclose(File*);
+void		filedelete(File*, uint, uint);
+void		filedeltext(File*, Text*);
+void		fileinsert(File*, uint, Rune*, uint);
+uint		fileload(File*, uint, int, int*);
+void		filemark(File*);
+void		filereset(File*);
+void		filesetname(File*, Rune*, int);
+void		fileundelete(File*, Buffer*, uint, uint);
+void		fileuninsert(File*, Buffer*, uint, uint);
+void		fileunsetname(File*, Buffer*);
+void		fileundo(File*, int, uint*, uint*);
+uint		fileredoseq(File*);
+
+enum	/* Text.what */
+{
+	Columntag,
+	Rowtag,
+	Tag,
+	Body,
+};
+
+struct Text
+{
+	File		*file;
+	Frame	fr;
+	Reffont	*reffont;
+	uint	org;
+	uint	q0;
+	uint	q1;
+	int	what;
+	int	tabstop;
+	Window	*w;
+	Rectangle scrollr;
+	Rectangle lastsr;
+	Rectangle all;
+	Row		*row;
+	Column	*col;
+
+	uint	eq0;	/* start of typing for ESC */
+	uint	cq0;	/* cache position */
+	int		ncache;	/* storage for insert */
+	int		ncachealloc;
+	Rune	*cache;
+	int	nofill;
+};
+
+uint		textbacknl(Text*, uint, uint);
+uint		textbsinsert(Text*, uint, Rune*, uint, int, int*);
+int		textbswidth(Text*, Rune);
+int		textclickmatch(Text*, int, int, int, uint*);
+void		textclose(Text*);
+void		textcolumnate(Text*, Dirlist**, int);
+void		textcommit(Text*, int);
+void		textconstrain(Text*, uint, uint, uint*, uint*);
+void		textdelete(Text*, uint, uint, int);
+void		textdoubleclick(Text*, uint*, uint*);
+void		textfill(Text*);
+void		textframescroll(Text*, int);
+void		textinit(Text*, File*, Rectangle, Reffont*, Image**);
+void		textinsert(Text*, uint, Rune*, uint, int);
+uint		textload(Text*, uint, char*, int);
+Rune		textreadc(Text*, uint);
+void		textredraw(Text*, Rectangle, Font*, Image*, int);
+void		textreset(Text*);
+int		textresize(Text*, Rectangle);
+void		textscrdraw(Text*);
+void		textscroll(Text*, int);
+void		textselect(Text*);
+int		textselect2(Text*, uint*, uint*, Text**);
+int		textselect23(Text*, uint*, uint*, Image*, int);
+int		textselect3(Text*, uint*, uint*);
+void		textsetorigin(Text*, uint, int);
+void		textsetselect(Text*, uint, uint);
+void		textshow(Text*, uint, uint, int);
+void		texttype(Text*, Rune);
+
+struct Window
+{
+	QLock	lk;
+	Ref	ref;
+	Text		tag;
+	Text		body;
+	Rectangle	r;
+	uchar	isdir;
+	uchar	isscratch;
+	uchar	filemenu;
+	uchar	dirty;
+	int		id;
+	Range	addr;
+	Range	limit;
+	uchar	nopen[QMAX];
+	uchar	nomark;
+	uchar	noscroll;
+	Range	wrselrange;
+	int		rdselfd;
+	int		neditwrsel;
+	Column	*col;
+	Xfid		*eventx;
+	char		*events;
+	int		nevents;
+	int		owner;
+	int		maxlines;
+	Dirlist	**dlp;
+	int		ndl;
+	int		putseq;
+	int		nincl;
+	Rune		**incl;
+	Reffont	*reffont;
+	QLock	ctllock;
+	uint		ctlfid;
+	char		*dumpstr;
+	char		*dumpdir;
+	int		dumpid;
+	int		utflastqid;
+	int		utflastboff;
+	int		utflastq;
+};
+
+void	wininit(Window*, Window*, Rectangle);
+void	winlock(Window*, int);
+void	winlock1(Window*, int);
+void	winunlock(Window*);
+void	wintype(Window*, Text*, Rune);
+void	winundo(Window*, int);
+void	winsetname(Window*, Rune*, int);
+void	winsettag(Window*);
+void	winsettag1(Window*);
+void	wincommit(Window*, Text*);
+int	winresize(Window*, Rectangle, int);
+void	winclose(Window*);
+void	windelete(Window*);
+int	winclean(Window*, int);
+void	windirfree(Window*);
+void	winevent(Window*, char*, ...);
+void	winmousebut(Window*);
+void	winaddincl(Window*, Rune*, int);
+void	wincleartag(Window*);
+void	winctlprint(Window*, char*, int);
+
+struct Column
+{
+	Rectangle r;
+	Text	tag;
+	Row		*row;
+	Window	**w;
+	int		nw;
+	int		safe;
+};
+
+void		colinit(Column*, Rectangle);
+Window*	coladd(Column*, Window*, Window*, int);
+void		colclose(Column*, Window*, int);
+void		colcloseall(Column*);
+void		colresize(Column*, Rectangle);
+Text*	colwhich(Column*, Point);
+void		coldragwin(Column*, Window*, int);
+void		colgrow(Column*, Window*, int);
+int		colclean(Column*);
+void		colsort(Column*);
+void		colmousebut(Column*);
+
+struct Row
+{
+	QLock	lk;
+	Rectangle r;
+	Text	tag;
+	Column	**col;
+	int		ncol;
+
+};
+
+void		rowinit(Row*, Rectangle);
+Column*	rowadd(Row*, Column *c, int);
+void		rowclose(Row*, Column*, int);
+Text*	rowwhich(Row*, Point);
+Column*	rowwhichcol(Row*, Point);
+void		rowresize(Row*, Rectangle);
+Text*	rowtype(Row*, Rune, Point);
+void		rowdragcol(Row*, Column*, int but);
+int		rowclean(Row*);
+void		rowdump(Row*, char*);
+void		rowload(Row*, char*, int);
+void		rowloadfonts(char*);
+
+struct Timer
+{
+	int		dt;
+	int		cancel;
+	Channel	*c;	/* chan(int) */
+	Timer	*next;
+};
+
+struct Command
+{
+	int		pid;
+	Rune		*name;
+	int		nname;
+	char		*text;
+	char		**av;
+	int		iseditcmd;
+	Mntdir	*md;
+	Command	*next;
+};
+
+struct Dirtab
+{
+	char	*name;
+	uchar	type;
+	uint	qid;
+	uint	perm;
+};
+
+struct Mntdir
+{
+	int		id;
+	int		ref;
+	Rune		*dir;
+	int		ndir;
+	Mntdir	*next;
+	int		nincl;
+	Rune		**incl;
+};
+
+struct Fid
+{
+	int		fid;
+	int		busy;
+	int		open;
+	Qid		qid;
+	Window	*w;
+	Dirtab	*dir;
+	Fid		*next;
+	Mntdir	*mntdir;
+	int		nrpart;
+	uchar	rpart[UTFmax];
+};
+
+
+struct Xfid
+{
+	void		*arg;	/* args to xfidinit */
+	Fcall	fcall;
+	Xfid	*next;
+	Channel	*c;		/* chan(void(*)(Xfid*)) */
+	Fid	*f;
+	uchar	*buf;
+	int	flushed;
+
+};
+
+void		xfidctl(void *);
+void		xfidflush(Xfid*);
+void		xfidopen(Xfid*);
+void		xfidclose(Xfid*);
+void		xfidread(Xfid*);
+void		xfidwrite(Xfid*);
+void		xfidctlwrite(Xfid*, Window*);
+void		xfideventread(Xfid*, Window*);
+void		xfideventwrite(Xfid*, Window*);
+void		xfidindexread(Xfid*);
+void		xfidutfread(Xfid*, Text*, uint, int);
+int		xfidruneread(Xfid*, Text*, uint, uint);
+
+struct Reffont
+{
+	Ref	ref;
+	Font	*f;
+
+};
+Reffont	*rfget(int, int, int, char*);
+void		rfclose(Reffont*);
+
+struct Rangeset
+{
+	Range	r[NRange];
+};
+
+struct Dirlist
+{
+	Rune	*r;
+	int		nr;
+	int		wid;
+};
+
+struct Expand
+{
+	uint	q0;
+	uint	q1;
+	Rune	*name;
+	int	nname;
+	char	*bname;
+	int	jump;
+	union{
+		Text	*at;
+		Rune	*ar;
+	} u;
+	int	(*agetc)(void*, uint);
+	int	a0;
+	int	a1;
+};
+
+enum
+{
+	/* fbufalloc() guarantees room off end of BUFSIZE */
+	BUFSIZE = Maxblock+IOHDRSZ,	/* size from fbufalloc() */
+	RBUFSIZE = BUFSIZE/sizeof(Rune),
+	EVENTSIZE = 256,
+	Scrollwid = 12,	/* width of scroll bar */
+	Scrollgap = 4,	/* gap right of scroll bar */
+	Margin = 4,	/* margin around text */
+	Border = 2,	/* line between rows, cols, windows */
+};
+
+#define	QID(w,q)	((w<<8)|(q))
+#define	WIN(q)	((((ulong)(q).path)>>8) & 0xFFFFFF)
+#define	FILE(q)	((q).path & 0xFF)
+
+enum
+{
+	FALSE,
+	TRUE,
+	XXX,
+};
+
+enum
+{
+	Empty	= 0,
+	Null		= '-',
+	Delete	= 'd',
+	Insert	= 'i',
+	Replace	= 'r',
+	Filename	= 'f',
+};
+
+enum	/* editing */
+{
+	Inactive	= 0,
+	Inserting,
+	Collecting,
+};
+
+uint		seq;
+uint		maxtab;	/* size of a tab, in units of the '0' character */
+
+Display		*display;
+Image		*screen;
+Font			*font;
+Mouse		*mouse;
+Mousectl		*mousectl;
+Keyboardctl	*keyboardctl;
+Reffont		reffont;
+Image		*modbutton;
+Image		*colbutton;
+Image		*button;
+Image		*but2col;
+Image		*but3col;
+Cursor		boxcursor;
+Row			row;
+int			timerpid;
+Disk			*disk;
+Text			*seltext;
+Text			*argtext;
+Text			*mousetext;	/* global because Text.close needs to clear it */
+Text			*typetext;		/* global because Text.close needs to clear it */
+Text			*barttext;		/* shared between mousetask and keyboardthread */
+int			bartflag;
+Window		*activewin;
+Column		*activecol;
+Buffer		snarfbuf;
+Rectangle		nullrect;
+int			fsyspid;
+char			*cputype;
+char			*objtype;
+char			*home;
+char			*fontnames[2];
+Image		*tagcols[NCOL];
+Image		*textcols[NCOL];
+int			plumbsendfd;
+int			plumbeditfd;
+extern char		wdir[];
+int			editing;
+int			erroutfd;
+int			messagesize;		/* negotiated in 9P version setup */
+
+Channel	*ckeyboard;	/* chan(Rune)[10] */
+Channel	*cplumb;		/* chan(Plumbmsg*) */
+Channel	*cwait;		/* chan(Waitmsg) */
+Channel	*ccommand;	/* chan(Command*) */
+Channel	*ckill;		/* chan(Rune*) */
+Channel	*cxfidalloc;	/* chan(Xfid*) */
+Channel	*cxfidfree;	/* chan(Xfid*) */
+Channel	*cnewwindow;	/* chan(Channel*) */
+Channel	*mouseexit0;	/* chan(int) */
+Channel	*mouseexit1;	/* chan(int) */
+Channel	*cexit;		/* chan(int) */
+Channel	*cerr;		/* chan(char*) */
+Channel	*cedit;		/* chan(int) */
+
+#define	STACK	32768
blob - /dev/null
blob + 857d9329be72214df886d0b118295374081ff1cf (mode 644)
--- /dev/null
+++ src/cmd/acme/disk.c
@@ -0,0 +1,129 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static	Block	*blist;
+
+int
+tempfile(void)
+{
+	char buf[128];
+	int i, fd;
+
+	snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser());
+	for(i='A'; i<='Z'; i++){
+		buf[5] = i;
+		if(access(buf, AEXIST) == 0)
+			continue;
+		fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
+		if(fd >= 0)
+			return fd;
+	}
+	return -1;
+}
+
+Disk*
+diskinit()
+{
+	Disk *d;
+
+	d = emalloc(sizeof(Disk));
+	d->fd = tempfile();
+	if(d->fd < 0){
+		fprint(2, "acme: can't create temp file: %r\n");
+		threadexitsall("diskinit");
+	}
+	return d;
+}
+
+static
+uint
+ntosize(uint n, uint *ip)
+{
+	uint size;
+
+	if(n > Maxblock)
+		error("internal error: ntosize");
+	size = n;
+	if(size & (Blockincr-1))
+		size += Blockincr - (size & (Blockincr-1));
+	/* last bucket holds blocks of exactly Maxblock */
+	if(ip)
+		*ip = size/Blockincr;
+	return size * sizeof(Rune);
+}
+
+Block*
+disknewblock(Disk *d, uint n)
+{
+	uint i, j, size;
+	Block *b;
+
+	size = ntosize(n, &i);
+	b = d->free[i];
+	if(b)
+		d->free[i] = b->u.next;
+	else{
+		/* allocate in chunks to reduce malloc overhead */
+		if(blist == nil){
+			blist = emalloc(100*sizeof(Block));
+			for(j=0; j<100-1; j++)
+				blist[j].u.next = &blist[j+1];
+		}
+		b = blist;
+		blist = b->u.next;
+		b->addr = d->addr;
+		d->addr += size;
+	}
+	b->u.n = n;
+	return b;
+}
+
+void
+diskrelease(Disk *d, Block *b)
+{
+	uint i;
+
+	ntosize(b->u.n, &i);
+	b->u.next = d->free[i];
+	d->free[i] = b;
+}
+
+void
+diskwrite(Disk *d, Block **bp, Rune *r, uint n)
+{
+	int size, nsize;
+	Block *b;
+
+	b = *bp;
+	size = ntosize(b->u.n, nil);
+	nsize = ntosize(n, nil);
+	if(size != nsize){
+		diskrelease(d, b);
+		b = disknewblock(d, n);
+		*bp = b;
+	}
+	if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+		error("write error to temp file");
+	b->u.n = n;
+}
+
+void
+diskread(Disk *d, Block *b, Rune *r, uint n)
+{
+	if(n > b->u.n)
+		error("internal error: diskread");
+
+	ntosize(b->u.n, nil);
+	if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+		error("read error from temp file");
+}
blob - /dev/null
blob + 677bafb19d5c3cb9a0870b76d6b500173c8a08e6 (mode 644)
--- /dev/null
+++ src/cmd/acme/ecmd.c
@@ -0,0 +1,1325 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "edit.h"
+#include "fns.h"
+
+int	Glooping;
+int	nest;
+char	Enoname[] = "no file name given";
+
+Address	addr;
+File	*menu;
+Rangeset	sel;
+extern	Text*	curtext;
+Rune	*collection;
+int	ncollection;
+
+int	append(File*, Cmd*, long);
+int	pdisplay(File*);
+void	pfilename(File*);
+void	looper(File*, Cmd*, int);
+void	filelooper(Cmd*, int);
+void	linelooper(File*, Cmd*);
+Address	lineaddr(long, Address, int);
+int	filematch(File*, String*);
+File	*tofile(String*);
+Rune*	cmdname(File *f, String *s, int);
+void	runpipe(Text*, int, Rune*, int, int);
+
+void
+clearcollection(void)
+{
+	free(collection);
+	collection = nil;
+	ncollection = 0;
+}
+
+void
+resetxec(void)
+{
+	Glooping = nest = 0;
+	clearcollection();
+}
+
+void
+mkaddr(Address *a, File *f)
+{
+	a->r.q0 = f->curtext->q0;
+	a->r.q1 = f->curtext->q1;
+	a->f = f;
+}
+
+int
+cmdexec(Text *t, Cmd *cp)
+{
+	int i;
+	Addr *ap;
+	File *f;
+	Window *w;
+	Address dot;
+
+	if(t == nil)
+		w = nil;
+	else
+		w = t->w;
+	if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
+	    !utfrune("bBnqUXY!", cp->cmdc) &&
+	    !(cp->cmdc=='D' && cp->u.text))
+		editerror("no current window");
+	i = cmdlookup(cp->cmdc);	/* will be -1 for '{' */
+	f = nil;
+	if(t && t->w){
+		t = &t->w->body;
+		f = t->file;
+		f->curtext = t;
+	}
+	if(i>=0 && cmdtab[i].defaddr != aNo){
+		if((ap=cp->addr)==0 && cp->cmdc!='\n'){
+			cp->addr = ap = newaddr();
+			ap->type = '.';
+			if(cmdtab[i].defaddr == aAll)
+				ap->type = '*';
+		}else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
+			ap->next = newaddr();
+			ap->next->type = '.';
+			if(cmdtab[i].defaddr == aAll)
+				ap->next->type = '*';
+		}
+		if(cp->addr){	/* may be false for '\n' (only) */
+			static Address none = {0,0,nil};
+			if(f){
+				mkaddr(&dot, f);
+				addr = cmdaddress(ap, dot, 0);
+			}else	/* a " */
+				addr = cmdaddress(ap, none, 0);
+			f = addr.f;
+			t = f->curtext;
+		}
+	}
+	switch(cp->cmdc){
+	case '{':
+		mkaddr(&dot, f);
+		if(cp->addr != nil)
+			dot = cmdaddress(cp->addr, dot, 0);
+		for(cp = cp->u.cmd; cp; cp = cp->next){
+			if(dot.r.q1 > t->file->b.nc)
+				editerror("dot extends past end of buffer during { command");
+			t->q0 = dot.r.q0;
+			t->q1 = dot.r.q1;
+			cmdexec(t, cp);
+		}
+		break;
+	default:
+		if(i < 0)
+			editerror("unknown command %c in cmdexec", cp->cmdc);
+		i = (*cmdtab[i].fn)(t, cp);
+		return i;
+	}
+	return 1;
+}
+
+char*
+edittext(Window *w, int q, Rune *r, int nr)
+{
+	File *f;
+
+	f = w->body.file;
+	switch(editing){
+	case Inactive:
+		return "permission denied";
+	case Inserting:
+		w->neditwrsel += nr;
+		eloginsert(f, q, r, nr);
+		return nil;
+	case Collecting:
+		collection = runerealloc(collection, ncollection+nr+1);
+		runemove(collection+ncollection, r, nr);
+		ncollection += nr;
+		collection[ncollection] = '\0';
+		return nil;
+	default:
+		return "unknown state in edittext";
+	}
+}
+
+/* string is known to be NUL-terminated */
+Rune*
+filelist(Text *t, Rune *r, int nr)
+{
+	if(nr == 0)
+		return nil;
+	r = skipbl(r, nr, &nr);
+	if(r[0] != '<')
+		return runestrdup(r);
+	/* use < command to collect text */
+	clearcollection();
+	runpipe(t, '<', r+1, nr-1, Collecting);
+	return collection;
+}
+
+int
+a_cmd(Text *t, Cmd *cp)
+{
+	return append(t->file, cp, addr.r.q1);
+}
+
+int
+b_cmd(Text *t, Cmd *cp)
+{
+	File *f;
+
+	USED(t);
+	f = tofile(cp->u.text);
+	if(nest == 0)
+		pfilename(f);
+	curtext = f->curtext;
+	return TRUE;
+}
+
+int
+B_cmd(Text *t, Cmd *cp)
+{
+	Rune *list, *r, *s;
+	int nr;
+
+	list = filelist(t, cp->u.text->r, cp->u.text->n);
+	if(list == nil)
+		editerror(Enoname);
+	r = list;
+	nr = runestrlen(r);
+	r = skipbl(r, nr, &nr);
+	if(nr == 0)
+		new(t, t, nil, 0, 0, r, 0);
+	else while(nr > 0){
+		s = findbl(r, nr, &nr);
+		*s = '\0';
+		new(t, t, nil, 0, 0, r, runestrlen(r));
+		if(nr > 0)
+			r = skipbl(s+1, nr-1, &nr);
+	}
+	clearcollection();
+	return TRUE;
+}
+
+int
+c_cmd(Text *t, Cmd *cp)
+{
+	elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
+	t->q0 = addr.r.q0;
+	t->q1 = addr.r.q0+cp->u.text->n;
+	return TRUE;
+}
+
+int
+d_cmd(Text *t, Cmd *cp)
+{
+	USED(cp);
+	if(addr.r.q1 > addr.r.q0)
+		elogdelete(t->file, addr.r.q0, addr.r.q1);
+	t->q0 = addr.r.q0;
+	t->q1 = addr.r.q0;
+	return TRUE;
+}
+
+void
+D1(Text *t)
+{
+	if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
+		colclose(t->col, t->w, TRUE);
+}
+
+int
+D_cmd(Text *t, Cmd *cp)
+{
+	Rune *list, *r, *s, *n;
+	int nr, nn;
+	Window *w;
+	Runestr dir, rs;
+	char buf[128];
+
+	list = filelist(t, cp->u.text->r, cp->u.text->n);
+	if(list == nil){
+		D1(t);
+		return TRUE;
+	}
+	dir = dirname(t, nil, 0);
+	r = list;
+	nr = runestrlen(r);
+	r = skipbl(r, nr, &nr);
+	do{
+		s = findbl(r, nr, &nr);
+		*s = '\0';
+		/* first time through, could be empty string, meaning delete file empty name */
+		nn = runestrlen(r);
+		if(r[0]=='/' || nn==0 || dir.nr==0){
+			rs.r = runestrdup(r);
+			rs.nr = nn;
+		}else{
+			n = runemalloc(dir.nr+1+nn);
+			runemove(n, dir.r, dir.nr);
+			n[dir.nr] = '/';
+			runemove(n+dir.nr+1, r, nn);
+			rs = cleanrname((Runestr){n, dir.nr+1+nn});
+		}
+		w = lookfile(rs.r, rs.nr);
+		if(w == nil){
+			snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
+			free(rs.r);
+			editerror(buf);
+		}
+		free(rs.r);
+		D1(&w->body);
+		if(nr > 0)
+			r = skipbl(s+1, nr-1, &nr);
+	}while(nr > 0);
+	clearcollection();
+	free(dir.r);
+	return TRUE;
+}
+
+static int
+readloader(void *v, uint q0, Rune *r, int nr)
+{
+	if(nr > 0)
+		eloginsert(v, q0, r, nr);
+	return 0;
+}
+
+int
+e_cmd(Text *t, Cmd *cp)
+{
+	Rune *name;
+	File *f;
+	int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
+	char *s, tmp[128];
+	Dir *d;
+
+	f = t->file;
+	q0 = addr.r.q0;
+	q1 = addr.r.q1;
+	if(cp->cmdc == 'e'){
+		if(winclean(t->w, TRUE)==FALSE)
+			editerror("");	/* winclean generated message already */
+		q0 = 0;
+		q1 = f->b.nc;
+	}
+	allreplaced = (q0==0 && q1==f->b.nc);
+	name = cmdname(f, cp->u.text, cp->cmdc=='e');
+	if(name == nil)
+		editerror(Enoname);
+	i = runestrlen(name);
+	samename = runeeq(name, i, t->file->name, t->file->nname);
+	s = runetobyte(name, i);
+	free(name);
+	fd = open(s, OREAD);
+	if(fd < 0){
+		snprint(tmp, sizeof tmp, "can't open %s: %r", s);
+		free(s);
+		editerror(tmp);
+	}
+	d = dirfstat(fd);
+	isdir = (d!=nil && (d->qid.type&QTDIR));
+	free(d);
+	if(isdir){
+		close(fd);
+		snprint(tmp, sizeof tmp, "%s is a directory", s);
+		free(s);
+		editerror(tmp);
+	}
+	elogdelete(f, q0, q1);
+	nulls = 0;
+	loadfile(fd, q1, &nulls, readloader, f);
+	free(s);
+	close(fd);
+	if(nulls)
+		warning(nil, "%s: NUL bytes elided\n", s);
+	else if(allreplaced && samename)
+		f->editclean = TRUE;
+	return TRUE;
+}
+
+static Rune Lempty[] = { 0 };
+int
+f_cmd(Text *t, Cmd *cp)
+{
+	Rune *name;
+	String *str;
+	String empty;
+
+	if(cp->u.text == nil){
+		empty.n = 0;
+		empty.r = Lempty;
+		str = &empty;
+	}else
+		str = cp->u.text;
+	name = cmdname(t->file, str, TRUE);
+	free(name);
+	pfilename(t->file);
+	return TRUE;
+}
+
+int
+g_cmd(Text *t, Cmd *cp)
+{
+	if(t->file != addr.f){
+		warning(nil, "internal error: g_cmd f!=addr.f\n");
+		return FALSE;
+	}
+	if(rxcompile(cp->re->r) == FALSE)
+		editerror("bad regexp in g command");
+	if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
+		t->q0 = addr.r.q0;
+		t->q1 = addr.r.q1;
+		return cmdexec(t, cp->u.cmd);
+	}
+	return TRUE;
+}
+
+int
+i_cmd(Text *t, Cmd *cp)
+{
+	return append(t->file, cp, addr.r.q0);
+}
+
+void
+copy(File *f, Address addr2)
+{
+	long p;
+	int ni;
+	Rune *buf;
+
+	buf = fbufalloc();
+	for(p=addr.r.q0; p<addr.r.q1; p+=ni){
+		ni = addr.r.q1-p;
+		if(ni > RBUFSIZE)
+			ni = RBUFSIZE;
+		bufread(&f->b, p, buf, ni);
+		eloginsert(addr2.f, addr2.r.q1, buf, ni);
+	}
+	fbuffree(buf);
+}
+
+void
+move(File *f, Address addr2)
+{
+	if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
+		elogdelete(f, addr.r.q0, addr.r.q1);
+		copy(f, addr2);
+	}else if(addr.r.q0 >= addr2.r.q1){
+		copy(f, addr2);
+		elogdelete(f, addr.r.q0, addr.r.q1);
+	}else
+		error("move overlaps itself");
+}
+
+int
+m_cmd(Text *t, Cmd *cp)
+{
+	Address dot, addr2;
+
+	mkaddr(&dot, t->file);
+	addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
+	if(cp->cmdc == 'm')
+		move(t->file, addr2);
+	else
+		copy(t->file, addr2);
+	return TRUE;
+}
+
+int
+p_cmd(Text *t, Cmd *cp)
+{
+	USED(cp);
+	return pdisplay(t->file);
+}
+
+int
+s_cmd(Text *t, Cmd *cp)
+{
+	int i, j, k, c, m, n, nrp, didsub;
+	long p1, op, delta;
+	String *buf;
+	Rangeset *rp;
+	char *err;
+	Rune *rbuf;
+
+	n = cp->num;
+	op= -1;
+	if(rxcompile(cp->re->r) == FALSE)
+		editerror("bad regexp in s command");
+	nrp = 0;
+	rp = nil;
+	delta = 0;
+	didsub = FALSE;
+	for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
+		if(sel.r[0].q0 == sel.r[0].q1){	/* empty match? */
+			if(sel.r[0].q0 == op){
+				p1++;
+				continue;
+			}
+			p1 = sel.r[0].q1+1;
+		}else
+			p1 = sel.r[0].q1;
+		op = sel.r[0].q1;
+		if(--n>0)
+			continue;
+		nrp++;
+		rp = erealloc(rp, nrp*sizeof(Rangeset));
+		rp[nrp-1] = sel;
+	}
+	rbuf = fbufalloc();
+	buf = allocstring(0);
+	for(m=0; m<nrp; m++){
+		buf->n = 0;
+		buf->r[0] = L'\0';
+		sel = rp[m];
+		for(i = 0; i<cp->u.text->n; i++)
+			if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
+				c = cp->u.text->r[++i];
+				if('1'<=c && c<='9') {
+					j = c-'0';
+					if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
+						err = "replacement string too long";
+						goto Err;
+					}
+					bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
+					for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
+						Straddc(buf, rbuf[k]);
+				}else
+				 	Straddc(buf, c);
+			}else if(c!='&')
+				Straddc(buf, c);
+			else{
+				if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
+					err = "right hand side too long in substitution";
+					goto Err;
+				}
+				bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
+				for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
+					Straddc(buf, rbuf[k]);
+			}
+		elogreplace(t->file, sel.r[0].q0, sel.r[0].q1,  buf->r, buf->n);
+		delta -= sel.r[0].q1-sel.r[0].q0;
+		delta += buf->n;
+		didsub = 1;
+		if(!cp->flag)
+			break;
+	}
+	free(rp);
+	freestring(buf);
+	fbuffree(rbuf);
+	if(!didsub && nest==0)
+		editerror("no substitution");
+	t->q0 = addr.r.q0;
+	t->q1 = addr.r.q1+delta;
+	return TRUE;
+
+Err:
+	free(rp);
+	freestring(buf);
+	fbuffree(rbuf);
+	editerror(err);
+	return FALSE;
+}
+
+int
+u_cmd(Text *t, Cmd *cp)
+{
+	int n, oseq, flag;
+
+	n = cp->num;
+	flag = TRUE;
+	if(n < 0){
+		n = -n;
+		flag = FALSE;
+	}
+	oseq = -1;
+	while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
+		oseq = t->file->seq;
+		undo(t, nil, nil, flag, 0, nil, 0);
+	}
+	return TRUE;
+}
+
+int
+w_cmd(Text *t, Cmd *cp)
+{
+	Rune *r;
+	File *f;
+
+	f = t->file;
+	if(f->seq == seq)
+		editerror("can't write file with pending modifications");
+	r = cmdname(f, cp->u.text, FALSE);
+	if(r == nil)
+		editerror("no name specified for 'w' command");
+	putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
+	/* r is freed by putfile */
+	return TRUE;
+}
+
+int
+x_cmd(Text *t, Cmd *cp)
+{
+	if(cp->re)
+		looper(t->file, cp, cp->cmdc=='x');
+	else
+		linelooper(t->file, cp);
+	return TRUE;
+}
+
+int
+X_cmd(Text *t, Cmd *cp)
+{
+	USED(t);
+
+	filelooper(cp, cp->cmdc=='X');
+	return TRUE;
+}
+
+void
+runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
+{
+	Rune *r, *s;
+	int n;
+	Runestr dir;
+	Window *w;
+
+	r = skipbl(cr, ncr, &n);
+	if(n == 0)
+		editerror("no command specified for >");
+	w = nil;
+	if(state == Inserting){
+		w = t->w;
+		t->q0 = addr.r.q0;
+		t->q1 = addr.r.q1;
+		w->neditwrsel = 0;
+		if(cmd == '<' || cmd=='|')
+			elogdelete(t->file, t->q0, t->q1);
+	}
+	s = runemalloc(n+2);
+	s[0] = cmd;
+	runemove(s+1, r, n);
+	n++;
+	dir.r = nil;
+	dir.nr = 0;
+	if(t != nil)
+		dir = dirname(t, nil, 0);
+	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
+		free(dir.r);
+		dir.r = nil;
+		dir.nr = 0;
+	}
+	editing = state;
+	if(t!=nil && t->w!=nil)
+		incref(&t->w->ref);	/* run will decref */
+	run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
+	free(s);
+	if(t!=nil && t->w!=nil)
+		winunlock(t->w);
+	qunlock(&row.lk);
+	recvul(cedit);
+	qlock(&row.lk);
+	editing = Inactive;
+	if(t!=nil && t->w!=nil)
+		winlock(t->w, 'M');
+	if(state == Inserting){
+		t->q0 = addr.r.q0;
+		t->q1 = addr.r.q0 + t->w->neditwrsel;
+	}
+}
+
+int
+pipe_cmd(Text *t, Cmd *cp)
+{
+	runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
+	return TRUE;
+}
+
+long
+nlcount(Text *t, long q0, long q1)
+{
+	long nl;
+	Rune *buf;
+	int i, nbuf;
+
+	buf = fbufalloc();
+	nbuf = 0;
+	i = nl = 0;
+	while(q0 < q1){
+		if(i == nbuf){
+			nbuf = q1-q0;
+			if(nbuf > RBUFSIZE)
+				nbuf = RBUFSIZE;
+			bufread(&t->file->b, q0, buf, nbuf);
+			i = 0;
+		}
+		if(buf[i++] == '\n')
+			nl++;
+		q0++;
+	}
+	fbuffree(buf);
+	return nl;
+}
+
+void
+printposn(Text *t, int charsonly)
+{
+	long l1, l2;
+
+	if (t != nil && t->file != nil && t->file->name != nil)
+		warning(nil, "%.*S:", t->file->nname, t->file->name);
+	if(!charsonly){
+		l1 = 1+nlcount(t, 0, addr.r.q0);
+		l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
+		/* check if addr ends with '\n' */
+		if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
+			--l2;
+		warning(nil, "%lud", l1);
+		if(l2 != l1)
+			warning(nil, ",%lud", l2);
+		warning(nil, "\n");
+		return;
+	}
+	warning(nil, "#%d", addr.r.q0);
+	if(addr.r.q1 != addr.r.q0)
+		warning(nil, ",#%d", addr.r.q1);
+	warning(nil, "\n");
+}
+
+int
+eq_cmd(Text *t, Cmd *cp)
+{
+	int charsonly;
+
+	switch(cp->u.text->n){
+	case 0:
+		charsonly = FALSE;
+		break;
+	case 1:
+		if(cp->u.text->r[0] == '#'){
+			charsonly = TRUE;
+			break;
+		}
+	default:
+		SET(charsonly);
+		editerror("newline expected");
+	}
+	printposn(t, charsonly);
+	return TRUE;
+}
+
+int
+nl_cmd(Text *t, Cmd *cp)
+{
+	Address a;
+	File *f;
+
+	f = t->file;
+	if(cp->addr == 0){
+		/* First put it on newline boundaries */
+		mkaddr(&a, f);
+		addr = lineaddr(0, a, -1);
+		a = lineaddr(0, a, 1);
+		addr.r.q1 = a.r.q1;
+		if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
+			mkaddr(&a, f);
+			addr = lineaddr(1, a, 1);
+		}
+	}
+	textshow(t, addr.r.q0, addr.r.q1, 1);
+	return TRUE;
+}
+
+int
+append(File *f, Cmd *cp, long p)
+{
+	if(cp->u.text->n > 0)
+		eloginsert(f, p, cp->u.text->r, cp->u.text->n);
+	f->curtext->q0 = p;
+	f->curtext->q1 = p+cp->u.text->n;
+	return TRUE;
+}
+
+int
+pdisplay(File *f)
+{
+	long p1, p2;
+	int np;
+	Rune *buf;
+
+	p1 = addr.r.q0;
+	p2 = addr.r.q1;
+	if(p2 > f->b.nc)
+		p2 = f->b.nc;
+	buf = fbufalloc();
+	while(p1 < p2){
+		np = p2-p1;
+		if(np>RBUFSIZE-1)
+			np = RBUFSIZE-1;
+		bufread(&f->b, p1, buf, np);
+		buf[np] = L'\0';
+		warning(nil, "%S", buf);
+		p1 += np;
+	}
+	fbuffree(buf);
+	f->curtext->q0 = addr.r.q0;
+	f->curtext->q1 = addr.r.q1;
+	return TRUE;
+}
+
+void
+pfilename(File *f)
+{
+	int dirty;
+	Window *w;
+
+	w = f->curtext->w;
+	/* same check for dirty as in settag, but we know ncache==0 */
+	dirty = !w->isdir && !w->isscratch && f->mod;
+	warning(nil, "%c%c%c %.*S\n", " '"[dirty],
+		'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
+}
+
+void
+loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
+{
+	long i;
+
+	for(i=0; i<nrp; i++){
+		f->curtext->q0 = rp[i].q0;
+		f->curtext->q1 = rp[i].q1;
+		cmdexec(f->curtext, cp);
+	}
+}
+
+void
+looper(File *f, Cmd *cp, int xy)
+{
+	long p, op, nrp;
+	Range r, tr;
+	Range *rp;
+
+	r = addr.r;
+	op= xy? -1 : r.q0;
+	nest++;
+	if(rxcompile(cp->re->r) == FALSE)
+		editerror("bad regexp in %c command", cp->cmdc);
+	nrp = 0;
+	rp = nil;
+	for(p = r.q0; p<=r.q1; ){
+		if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
+			if(xy || op>r.q1)
+				break;
+			tr.q0 = op, tr.q1 = r.q1;
+			p = r.q1+1;	/* exit next loop */
+		}else{
+			if(sel.r[0].q0==sel.r[0].q1){	/* empty match? */
+				if(sel.r[0].q0==op){
+					p++;
+					continue;
+				}
+				p = sel.r[0].q1+1;
+			}else
+				p = sel.r[0].q1;
+			if(xy)
+				tr = sel.r[0];
+			else
+				tr.q0 = op, tr.q1 = sel.r[0].q0;
+		}
+		op = sel.r[0].q1;
+		nrp++;
+		rp = erealloc(rp, nrp*sizeof(Range));
+		rp[nrp-1] = tr;
+	}
+	loopcmd(f, cp->u.cmd, rp, nrp);
+	free(rp);
+	--nest;
+}
+
+void
+linelooper(File *f, Cmd *cp)
+{
+	long nrp, p;
+	Range r, linesel;
+	Address a, a3;
+	Range *rp;
+
+	nest++;
+	nrp = 0;
+	rp = nil;
+	r = addr.r;
+	a3.f = f;
+	a3.r.q0 = a3.r.q1 = r.q0;
+	a = lineaddr(0, a3, 1);
+	linesel = a.r;
+	for(p = r.q0; p<r.q1; p = a3.r.q1){
+		a3.r.q0 = a3.r.q1;
+		if(p!=r.q0 || linesel.q1==p){
+			a = lineaddr(1, a3, 1);
+			linesel = a.r;
+		}
+		if(linesel.q0 >= r.q1)
+			break;
+		if(linesel.q1 >= r.q1)
+			linesel.q1 = r.q1;
+		if(linesel.q1 > linesel.q0)
+			if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
+				a3.r = linesel;
+				nrp++;
+				rp = erealloc(rp, nrp*sizeof(Range));
+				rp[nrp-1] = linesel;
+				continue;
+			}
+		break;
+	}
+	loopcmd(f, cp->u.cmd, rp, nrp);
+	free(rp);
+	--nest;
+}
+
+struct Looper
+{
+	Cmd *cp;
+	int	XY;
+	Window	**w;
+	int	nw;
+} loopstruct;	/* only one; X and Y can't nest */
+
+void
+alllooper(Window *w, void *v)
+{
+	Text *t;
+	struct Looper *lp;
+	Cmd *cp;
+
+	lp = v;
+	cp = lp->cp;
+//	if(w->isscratch || w->isdir)
+//		return;
+	t = &w->body;
+	/* only use this window if it's the current window for the file */
+	if(t->file->curtext != t)
+		return;
+//	if(w->nopen[QWevent] > 0)
+//		return;
+	/* no auto-execute on files without names */
+	if(cp->re==nil && t->file->nname==0)
+		return;
+	if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
+		lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
+		lp->w[lp->nw++] = w;
+	}
+}
+
+void
+alllocker(Window *w, void *v)
+{
+	if(v)
+		incref(&w->ref);
+	else
+		winclose(w);
+}
+
+void
+filelooper(Cmd *cp, int XY)
+{
+	int i;
+
+	if(Glooping++)
+		editerror("can't nest %c command", "YX"[XY]);
+	nest++;
+
+	loopstruct.cp = cp;
+	loopstruct.XY = XY;
+	if(loopstruct.w)	/* error'ed out last time */
+		free(loopstruct.w);
+	loopstruct.w = nil;
+	loopstruct.nw = 0;
+	allwindows(alllooper, &loopstruct);
+	/*
+	 * add a ref to all windows to keep safe windows accessed by X
+	 * that would not otherwise have a ref to hold them up during
+	 * the shenanigans.
+	 */
+	allwindows(alllocker, (void*)1);
+	for(i=0; i<loopstruct.nw; i++)
+		cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
+	allwindows(alllocker, (void*)0);
+	free(loopstruct.w);
+	loopstruct.w = nil;
+
+	--Glooping;
+	--nest;
+}
+
+void
+nextmatch(File *f, String *r, long p, int sign)
+{
+	if(rxcompile(r->r) == FALSE)
+		editerror("bad regexp in command address");
+	if(sign >= 0){
+		if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
+			editerror("no match for regexp");
+		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
+			if(++p>f->b.nc)
+				p = 0;
+			if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
+				editerror("address");
+		}
+	}else{
+		if(!rxbexecute(f->curtext, p, &sel))
+			editerror("no match for regexp");
+		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
+			if(--p<0)
+				p = f->b.nc;
+			if(!rxbexecute(f->curtext, p, &sel))
+				editerror("address");
+		}
+	}
+}
+
+File	*matchfile(String*);
+Address	charaddr(long, Address, int);
+Address	lineaddr(long, Address, int);
+
+Address
+cmdaddress(Addr *ap, Address a, int sign)
+{
+	File *f = a.f;
+	Address a1, a2;
+
+	do{
+		switch(ap->type){
+		case 'l':
+		case '#':
+			a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
+			break;
+
+		case '.':
+			mkaddr(&a, f);
+			break;
+
+		case '$':
+			a.r.q0 = a.r.q1 = f->b.nc;
+			break;
+
+		case '\'':
+editerror("can't handle '");
+//			a.r = f->mark;
+			break;
+
+		case '?':
+			sign = -sign;
+			if(sign == 0)
+				sign = -1;
+			/* fall through */
+		case '/':
+			nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
+			a.r = sel.r[0];
+			break;
+
+		case '"':
+			f = matchfile(ap->u.re);
+			mkaddr(&a, f);
+			break;
+
+		case '*':
+			a.r.q0 = 0, a.r.q1 = f->b.nc;
+			return a;
+
+		case ',':
+		case ';':
+			if(ap->u.left)
+				a1 = cmdaddress(ap->u.left, a, 0);
+			else
+				a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
+			if(ap->type == ';'){
+				f = a1.f;
+				a = a1;
+				f->curtext->q0 = a1.r.q0;
+				f->curtext->q1 = a1.r.q1;
+			}
+			if(ap->next)
+				a2 = cmdaddress(ap->next, a, 0);
+			else
+				a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
+			if(a1.f != a2.f)
+				editerror("addresses in different files");
+			a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
+			if(a.r.q1 < a.r.q0)
+				editerror("addresses out of order");
+			return a;
+
+		case '+':
+		case '-':
+			sign = 1;
+			if(ap->type == '-')
+				sign = -1;
+			if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
+				a = lineaddr(1L, a, sign);
+			break;
+		default:
+			error("cmdaddress");
+			return a;
+		}
+	}while(ap = ap->next);	/* assign = */
+	return a;
+}
+
+struct Tofile{
+	File		*f;
+	String	*r;
+};
+
+void
+alltofile(Window *w, void *v)
+{
+	Text *t;
+	struct Tofile *tp;
+
+	tp = v;
+	if(tp->f != nil)
+		return;
+	if(w->isscratch || w->isdir)
+		return;
+	t = &w->body;
+	/* only use this window if it's the current window for the file */
+	if(t->file->curtext != t)
+		return;
+//	if(w->nopen[QWevent] > 0)
+//		return;
+	if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
+		tp->f = t->file;
+}
+
+File*
+tofile(String *r)
+{
+	struct Tofile t;
+	String rr;
+
+	rr.r = skipbl(r->r, r->n, &rr.n);
+	t.f = nil;
+	t.r = &rr;
+	allwindows(alltofile, &t);
+	if(t.f == nil)
+		editerror("no such file\"%S\"", rr.r);
+	return t.f;
+}
+
+void
+allmatchfile(Window *w, void *v)
+{
+	struct Tofile *tp;
+	Text *t;
+
+	tp = v;
+	if(w->isscratch || w->isdir)
+		return;
+	t = &w->body;
+	/* only use this window if it's the current window for the file */
+	if(t->file->curtext != t)
+		return;
+//	if(w->nopen[QWevent] > 0)
+//		return;
+	if(filematch(w->body.file, tp->r)){
+		if(tp->f != nil)
+			editerror("too many files match \"%S\"", tp->r->r);
+		tp->f = w->body.file;
+	}
+}
+
+File*
+matchfile(String *r)
+{
+	struct Tofile tf;
+
+	tf.f = nil;
+	tf.r = r;
+	allwindows(allmatchfile, &tf);
+
+	if(tf.f == nil)
+		editerror("no file matches \"%S\"", r->r);
+	return tf.f;
+}
+
+int
+filematch(File *f, String *r)
+{
+	char *buf;
+	Rune *rbuf;
+	Window *w;
+	int match, i, dirty;
+	Rangeset s;
+
+	/* compile expr first so if we get an error, we haven't allocated anything */
+	if(rxcompile(r->r) == FALSE)
+		editerror("bad regexp in file match");
+	buf = fbufalloc();
+	w = f->curtext->w;
+	/* same check for dirty as in settag, but we know ncache==0 */
+	dirty = !w->isdir && !w->isscratch && f->mod;
+	snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
+		'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
+	rbuf = bytetorune(buf, &i);
+	fbuffree(buf);
+	match = rxexecute(nil, rbuf, 0, i, &s);
+	free(rbuf);
+	return match;
+}
+
+Address
+charaddr(long l, Address addr, int sign)
+{
+	if(sign == 0)
+		addr.r.q0 = addr.r.q1 = l;
+	else if(sign < 0)
+		addr.r.q1 = addr.r.q0 -= l;
+	else if(sign > 0)
+		addr.r.q0 = addr.r.q1 += l;
+	if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
+		editerror("address out of range");
+	return addr;
+}
+
+Address
+lineaddr(long l, Address addr, int sign)
+{
+	int n;
+	int c;
+	File *f = addr.f;
+	Address a;
+	long p;
+
+	a.f = f;
+	if(sign >= 0){
+		if(l == 0){
+			if(sign==0 || addr.r.q1==0){
+				a.r.q0 = a.r.q1 = 0;
+				return a;
+			}
+			a.r.q0 = addr.r.q1;
+			p = addr.r.q1-1;
+		}else{
+			if(sign==0 || addr.r.q1==0){
+				p = 0;
+				n = 1;
+			}else{
+				p = addr.r.q1-1;
+				n = textreadc(f->curtext, p++)=='\n';
+			}
+			while(n < l){
+				if(p >= f->b.nc)
+					editerror("address out of range");
+				if(textreadc(f->curtext, p++) == '\n')
+					n++;
+			}
+			a.r.q0 = p;
+		}
+		while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
+			;
+		a.r.q1 = p;
+	}else{
+		p = addr.r.q0;
+		if(l == 0)
+			a.r.q1 = addr.r.q0;
+		else{
+			for(n = 0; n<l; ){	/* always runs once */
+				if(p == 0){
+					if(++n != l)
+						editerror("address out of range");
+				}else{
+					c = textreadc(f->curtext, p-1);
+					if(c != '\n' || ++n != l)
+						p--;
+				}
+			}
+			a.r.q1 = p;
+			if(p > 0)
+				p--;
+		}
+		while(p > 0 && textreadc(f->curtext, p-1)!='\n')	/* lines start after a newline */
+			p--;
+		a.r.q0 = p;
+	}
+	return a;
+}
+
+struct Filecheck
+{
+	File	*f;
+	Rune	*r;
+	int nr;
+};
+
+void
+allfilecheck(Window *w, void *v)
+{
+	struct Filecheck *fp;
+	File *f;
+
+	fp = v;
+	f = w->body.file;
+	if(w->body.file == fp->f)
+		return;
+	if(runeeq(fp->r, fp->nr, f->name, f->nname))
+		warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
+}
+
+Rune*
+cmdname(File *f, String *str, int set)
+{
+	Rune *r, *s;
+	int n;
+	struct Filecheck fc;
+	Runestr newname;
+
+	r = nil;
+	n = str->n;
+	s = str->r;
+	if(n == 0){
+		/* no name; use existing */
+		if(f->nname == 0)
+			return nil;
+		r = runemalloc(f->nname+1);
+		runemove(r, f->name, f->nname);
+		return r;
+	}
+	s = skipbl(s, n, &n);
+	if(n == 0)
+		goto Return;
+
+	if(s[0] == '/'){
+		r = runemalloc(n+1);
+		runemove(r, s, n);
+	}else{
+		newname = dirname(f->curtext, runestrdup(s), n);
+		r = newname.r;
+		n = newname.nr;
+	}
+	fc.f = f;
+	fc.r = r;
+	fc.nr = n;
+	allwindows(allfilecheck, &fc);
+	if(f->nname == 0)
+		set = TRUE;
+
+    Return:
+	if(set && !runeeq(r, n, f->name, f->nname)){
+		filemark(f);
+		f->mod = TRUE;
+		f->curtext->w->dirty = TRUE;
+		winsetname(f->curtext->w, r, n);
+	}
+	return r;
+}
blob - /dev/null
blob + e052430ea475abb693ed01847e1bd2b6c3247639 (mode 644)
--- /dev/null
+++ src/cmd/acme/edit.c
@@ -0,0 +1,682 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "edit.h"
+#include "fns.h"
+
+static char	linex[]="\n";
+static char	wordx[]=" \t\n";
+struct cmdtab cmdtab[]={
+/*	cmdc	text	regexp	addr	defcmd	defaddr	count	token	 fn	*/
+	'\n',	0,	0,	0,	0,	aDot,	0,	0,	nl_cmd,
+	'a',	1,	0,	0,	0,	aDot,	0,	0,	a_cmd,
+	'b',	0,	0,	0,	0,	aNo,	0,	linex,	b_cmd,
+	'c',	1,	0,	0,	0,	aDot,	0,	0,	c_cmd,
+	'd',	0,	0,	0,	0,	aDot,	0,	0,	d_cmd,
+	'e',	0,	0,	0,	0,	aNo,	0,	wordx,	e_cmd,
+	'f',	0,	0,	0,	0,	aNo,	0,	wordx,	f_cmd,
+	'g',	0,	1,	0,	'p',	aDot,	0,	0,	g_cmd,
+	'i',	1,	0,	0,	0,	aDot,	0,	0,	i_cmd,
+	'm',	0,	0,	1,	0,	aDot,	0,	0,	m_cmd,
+	'p',	0,	0,	0,	0,	aDot,	0,	0,	p_cmd,
+	'r',	0,	0,	0,	0,	aDot,	0,	wordx,	e_cmd,
+	's',	0,	1,	0,	0,	aDot,	1,	0,	s_cmd,
+	't',	0,	0,	1,	0,	aDot,	0,	0,	m_cmd,
+	'u',	0,	0,	0,	0,	aNo,	2,	0,	u_cmd,
+	'v',	0,	1,	0,	'p',	aDot,	0,	0,	g_cmd,
+	'w',	0,	0,	0,	0,	aAll,	0,	wordx,	w_cmd,
+	'x',	0,	1,	0,	'p',	aDot,	0,	0,	x_cmd,
+	'y',	0,	1,	0,	'p',	aDot,	0,	0,	x_cmd,
+	'=',	0,	0,	0,	0,	aDot,	0,	linex,	eq_cmd,
+	'B',	0,	0,	0,	0,	aNo,	0,	linex,	B_cmd,
+	'D',	0,	0,	0,	0,	aNo,	0,	linex,	D_cmd,
+	'X',	0,	1,	0,	'f',	aNo,	0,	0,	X_cmd,
+	'Y',	0,	1,	0,	'f',	aNo,	0,	0,	X_cmd,
+	'<',	0,	0,	0,	0,	aDot,	0,	linex,	pipe_cmd,
+	'|',	0,	0,	0,	0,	aDot,	0,	linex,	pipe_cmd,
+	'>',	0,	0,	0,	0,	aDot,	0,	linex,	pipe_cmd,
+/* deliberately unimplemented:
+	'k',	0,	0,	0,	0,	aDot,	0,	0,	k_cmd,
+	'n',	0,	0,	0,	0,	aNo,	0,	0,	n_cmd,
+	'q',	0,	0,	0,	0,	aNo,	0,	0,	q_cmd,
+	'!',	0,	0,	0,	0,	aNo,	0,	linex,	plan9_cmd,
+ */
+	0,	0,	0,	0,	0,	0,	0,	0,
+};
+
+Cmd	*parsecmd(int);
+Addr	*compoundaddr(void);
+Addr	*simpleaddr(void);
+void	freecmd(void);
+void	okdelim(int);
+
+Rune	*cmdstartp;
+Rune *cmdendp;
+Rune	*cmdp;
+Channel	*editerrc;
+
+String	*lastpat;
+int	patset;
+
+List	cmdlist;
+List	addrlist;
+List	stringlist;
+Text	*curtext;
+int	editing = Inactive;
+
+String*	newstring(int);
+
+void
+editthread(void *v)
+{
+	Cmd *cmdp;
+
+	USED(v);
+	threadsetname("editthread");
+	while((cmdp=parsecmd(0)) != 0){
+//		ocurfile = curfile;
+//		loaded = curfile && !curfile->unread;
+		if(cmdexec(curtext, cmdp) == 0)
+			break;
+		freecmd();
+	}
+	sendp(editerrc, nil);
+}
+
+void
+allelogterm(Window *w, void *x)
+{
+	USED(x);
+	elogterm(w->body.file);
+}
+
+void
+alleditinit(Window *w, void *x)
+{
+	USED(x);
+	textcommit(&w->tag, TRUE);
+	textcommit(&w->body, TRUE);
+	w->body.file->editclean = FALSE;
+}
+
+void
+allupdate(Window *w, void *x)
+{
+	Text *t;
+	int i;
+	File *f;
+
+	USED(x);
+	t = &w->body;
+	f = t->file;
+	if(f->curtext != t)	/* do curtext only */
+		return;
+	if(f->elog.type == Null)
+		elogterm(f);
+	else if(f->elog.type != Empty){
+		elogapply(f);
+		if(f->editclean){
+			f->mod = FALSE;
+			for(i=0; i<f->ntext; i++)
+				f->text[i]->w->dirty = FALSE;
+		}
+	}
+	textsetselect(t, t->q0, t->q1);
+	textscrdraw(t);
+	winsettag(w);
+}
+
+void
+editerror(char *fmt, ...)
+{
+	va_list arg;
+	char *s;
+
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	freecmd();
+	allwindows(allelogterm, nil);	/* truncate the edit logs */
+	sendp(editerrc, s);
+	threadexits(nil);
+}
+
+void
+editcmd(Text *ct, Rune *r, uint n)
+{
+	char *err;
+
+	if(n == 0)
+		return;
+	if(2*n > RBUFSIZE){
+		warning(nil, "string too long\n");
+		return;
+	}
+
+	allwindows(alleditinit, nil);
+	if(cmdstartp)
+		free(cmdstartp);
+	cmdstartp = runemalloc(n+2);
+	runemove(cmdstartp, r, n);
+	if(r[n] != '\n')
+		cmdstartp[n++] = '\n';
+	cmdstartp[n] = '\0';
+	cmdendp = cmdstartp+n;
+	cmdp = cmdstartp;
+	if(ct->w == nil)
+		curtext = nil;
+	else
+		curtext = &ct->w->body;
+	resetxec();
+	if(editerrc == nil){
+		editerrc = chancreate(sizeof(char*), 0);
+		lastpat = allocstring(0);
+	}
+	threadcreate(editthread, nil, STACK);
+	err = recvp(editerrc);
+	editing = Inactive;
+	if(err != nil){
+		if(err[0] != '\0')
+			warning(nil, "Edit: %s\n", err);
+		free(err);
+	}
+
+	/* update everyone whose edit log has data */
+	allwindows(allupdate, nil);
+}
+
+int
+getch(void)
+{
+	if(*cmdp == *cmdendp)
+		return -1;
+	return *cmdp++;
+}
+
+int
+nextc(void)
+{
+	if(*cmdp == *cmdendp)
+		return -1;
+	return *cmdp;
+}
+
+void
+ungetch(void)
+{
+	if(--cmdp < cmdstartp)
+		error("ungetch");
+}
+
+long
+getnum(int signok)
+{
+	long n;
+	int c, sign;
+
+	n = 0;
+	sign = 1;
+	if(signok>1 && nextc()=='-'){
+		sign = -1;
+		getch();
+	}
+	if((c=nextc())<'0' || '9'<c)	/* no number defaults to 1 */
+		return sign;
+	while('0'<=(c=getch()) && c<='9')
+		n = n*10 + (c-'0');
+	ungetch();
+	return sign*n;
+}
+
+int
+cmdskipbl(void)
+{
+	int c;
+	do
+		c = getch();
+	while(c==' ' || c=='\t');
+	if(c >= 0)
+		ungetch();
+	return c;
+}
+
+/*
+ * Check that list has room for one more element.
+ */
+void
+growlist(List *l)
+{
+	if(l->u.listptr==0 || l->nalloc==0){
+		l->nalloc = INCR;
+		l->u.listptr = emalloc(INCR*sizeof(long));
+		l->nused = 0;
+	}else if(l->nused == l->nalloc){
+		l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(long));
+		memset((void*)(l->u.longptr+l->nalloc), 0, INCR*sizeof(long));
+		l->nalloc += INCR;
+	}
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+	memmove(&l->u.longptr[i], &l->u.longptr[i+1], (l->nused-(i+1))*sizeof(long));
+	l->nused--;
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, long val)
+{
+	growlist(l);
+	memmove(&l->u.longptr[i+1], &l->u.longptr[i], (l->nused-i)*sizeof(long));
+	l->u.longptr[i] = val;
+	l->nused++;
+}
+
+void
+listfree(List *l)
+{
+	free(l->u.listptr);
+	free(l);
+}
+
+String*
+allocstring(int n)
+{
+	String *s;
+
+	s = emalloc(sizeof(String));
+	s->n = n;
+	s->nalloc = n+10;
+	s->r = emalloc(s->nalloc*sizeof(Rune));
+	s->r[n] = '\0';
+	return s;
+}
+
+void
+freestring(String *s)
+{
+	free(s->r);
+	free(s);
+}
+
+Cmd*
+newcmd(void){
+	Cmd *p;
+
+	p = emalloc(sizeof(Cmd));
+	inslist(&cmdlist, cmdlist.nused, (long)p);
+	return p;
+}
+
+String*
+newstring(int n)
+{
+	String *p;
+
+	p = allocstring(n);
+	inslist(&stringlist, stringlist.nused, (long)p);
+	return p;
+}
+
+Addr*
+newaddr(void)
+{
+	Addr *p;
+
+	p = emalloc(sizeof(Addr));
+	inslist(&addrlist, addrlist.nused, (long)p);
+	return p;
+}
+
+void
+freecmd(void)
+{
+	int i;
+
+	while(cmdlist.nused > 0)
+		free(cmdlist.u.ucharptr[--cmdlist.nused]);
+	while(addrlist.nused > 0)
+		free(addrlist.u.ucharptr[--addrlist.nused]);
+	while(stringlist.nused>0){
+		i = --stringlist.nused;
+		freestring(stringlist.u.stringptr[i]);
+	}
+}
+
+void
+okdelim(int c)
+{
+	if(c=='\\' || ('a'<=c && c<='z')
+	|| ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+		editerror("bad delimiter %c\n", c);
+}
+
+void
+atnl(void)
+{
+	int c;
+
+	cmdskipbl();
+	c = getch();
+	if(c != '\n')
+		editerror("newline expected (saw %C)", c);
+}
+
+void
+Straddc(String *s, int c)
+{
+	if(s->n+1 >= s->nalloc){
+		s->nalloc += 10;
+		s->r = erealloc(s->r, s->nalloc*sizeof(Rune));
+	}
+	s->r[s->n++] = c;
+	s->r[s->n] = '\0';
+}
+
+void
+getrhs(String *s, int delim, int cmd)
+{
+	int c;
+
+	while((c = getch())>0 && c!=delim && c!='\n'){
+		if(c == '\\'){
+			if((c=getch()) <= 0)
+				error("bad right hand side");
+			if(c == '\n'){
+				ungetch();
+				c='\\';
+			}else if(c == 'n')
+				c='\n';
+			else if(c!=delim && (cmd=='s' || c!='\\'))	/* s does its own */
+				Straddc(s, '\\');
+		}
+		Straddc(s, c);
+	}
+	ungetch();	/* let client read whether delimiter, '\n' or whatever */
+}
+
+String *
+collecttoken(char *end)
+{
+	String *s = newstring(0);
+	int c;
+
+	while((c=nextc())==' ' || c=='\t')
+		Straddc(s, getch()); /* blanks significant for getname() */
+	while((c=getch())>0 && utfrune(end, c)==0)
+		Straddc(s, c);
+	if(c != '\n')
+		atnl();
+	return s;
+}
+
+String *
+collecttext(void)
+{
+	String *s;
+	int begline, i, c, delim;
+
+	s = newstring(0);
+	if(cmdskipbl()=='\n'){
+		getch();
+		i = 0;
+		do{
+			begline = i;
+			while((c = getch())>0 && c!='\n')
+				i++, Straddc(s, c);
+			i++, Straddc(s, '\n');
+			if(c < 0)
+				goto Return;
+		}while(s->r[begline]!='.' || s->r[begline+1]!='\n');
+		s->r[s->n-2] = '\0';
+	}else{
+		okdelim(delim = getch());
+		getrhs(s, delim, 'a');
+		if(nextc()==delim)
+			getch();
+		atnl();
+	}
+    Return:
+	return s;
+}
+
+int
+cmdlookup(int c)
+{
+	int i;
+
+	for(i=0; cmdtab[i].cmdc; i++)
+		if(cmdtab[i].cmdc == c)
+			return i;
+	return -1;
+}
+
+Cmd*
+parsecmd(int nest)
+{
+	int i, c;
+	struct cmdtab *ct;
+	Cmd *cp, *ncp;
+	Cmd cmd;
+
+	cmd.next = cmd.u.cmd = 0;
+	cmd.re = 0;
+	cmd.flag = cmd.num = 0;
+	cmd.addr = compoundaddr();
+	if(cmdskipbl() == -1)
+		return 0;
+	if((c=getch())==-1)
+		return 0;
+	cmd.cmdc = c;
+	if(cmd.cmdc=='c' && nextc()=='d'){	/* sleazy two-character case */
+		getch();		/* the 'd' */
+		cmd.cmdc='c'|0x100;
+	}
+	i = cmdlookup(cmd.cmdc);
+	if(i >= 0){
+		if(cmd.cmdc == '\n')
+			goto Return;	/* let nl_cmd work it all out */
+		ct = &cmdtab[i];
+		if(ct->defaddr==aNo && cmd.addr)
+			editerror("command takes no address");
+		if(ct->count)
+			cmd.num = getnum(ct->count);
+		if(ct->regexp){
+			/* x without pattern -> .*\n, indicated by cmd.re==0 */
+			/* X without pattern is all files */
+			if((ct->cmdc!='x' && ct->cmdc!='X') ||
+			   ((c = nextc())!=' ' && c!='\t' && c!='\n')){
+				cmdskipbl();
+				if((c = getch())=='\n' || c<0)
+					editerror("no address");
+				okdelim(c);
+				cmd.re = getregexp(c);
+				if(ct->cmdc == 's'){
+					cmd.u.text = newstring(0);
+					getrhs(cmd.u.text, c, 's');
+					if(nextc() == c){
+						getch();
+						if(nextc() == 'g')
+							cmd.flag = getch();
+					}
+			
+				}
+			}
+		}
+		if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0)
+			editerror("bad address");
+		if(ct->defcmd){
+			if(cmdskipbl() == '\n'){
+				getch();
+				cmd.u.cmd = newcmd();
+				cmd.u.cmd->cmdc = ct->defcmd;
+			}else if((cmd.u.cmd = parsecmd(nest))==0)
+				error("defcmd");
+		}else if(ct->text)
+			cmd.u.text = collecttext();
+		else if(ct->token)
+			cmd.u.text = collecttoken(ct->token);
+		else
+			atnl();
+	}else
+		switch(cmd.cmdc){
+		case '{':
+			cp = 0;
+			do{
+				if(cmdskipbl()=='\n')
+					getch();
+				ncp = parsecmd(nest+1);
+				if(cp)
+					cp->next = ncp;
+				else
+					cmd.u.cmd = ncp;
+			}while(cp = ncp);
+			break;
+		case '}':
+			atnl();
+			if(nest==0)
+				editerror("right brace with no left brace");
+			return 0;
+		default:
+			editerror("unknown command %c", cmd.cmdc);
+		}
+    Return:
+	cp = newcmd();
+	*cp = cmd;
+	return cp;
+}
+
+String*
+getregexp(int delim)
+{
+	String *buf, *r;
+	int i, c;
+
+	buf = allocstring(0);
+	for(i=0; ; i++){
+		if((c = getch())=='\\'){
+			if(nextc()==delim)
+				c = getch();
+			else if(nextc()=='\\'){
+				Straddc(buf, c);
+				c = getch();
+			}
+		}else if(c==delim || c=='\n')
+			break;
+		if(i >= RBUFSIZE)
+			editerror("regular expression too long");
+		Straddc(buf, c);
+	}
+	if(c!=delim && c)
+		ungetch();
+	if(buf->n > 0){
+		patset = TRUE;
+		freestring(lastpat);
+		lastpat = buf;
+	}else
+		freestring(buf);
+	if(lastpat->n == 0)
+		editerror("no regular expression defined");
+	r = newstring(lastpat->n);
+	runemove(r->r, lastpat->r, lastpat->n);	/* newstring put \0 at end */
+	return r;
+}
+
+Addr *
+simpleaddr(void)
+{
+	Addr addr;
+	Addr *ap, *nap;
+
+	addr.next = 0;
+	addr.u.left = 0;
+	switch(cmdskipbl()){
+	case '#':
+		addr.type = getch();
+		addr.num = getnum(1);
+		break;
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9': 
+		addr.num = getnum(1);
+		addr.type='l';
+		break;
+	case '/': case '?': case '"':
+		addr.u.re = getregexp(addr.type = getch());
+		break;
+	case '.':
+	case '$':
+	case '+':
+	case '-':
+	case '\'':
+		addr.type = getch();
+		break;
+	default:
+		return 0;
+	}
+	if(addr.next = simpleaddr())
+		switch(addr.next->type){
+		case '.':
+		case '$':
+		case '\'':
+			if(addr.type!='"')
+		case '"':
+				editerror("bad address syntax");
+			break;
+		case 'l':
+		case '#':
+			if(addr.type=='"')
+				break;
+			/* fall through */
+		case '/':
+		case '?':
+			if(addr.type!='+' && addr.type!='-'){
+				/* insert the missing '+' */
+				nap = newaddr();
+				nap->type='+';
+				nap->next = addr.next;
+				addr.next = nap;
+			}
+			break;
+		case '+':
+		case '-':
+			break;
+		default:
+			error("simpleaddr");
+		}
+	ap = newaddr();
+	*ap = addr;
+	return ap;
+}
+
+Addr *
+compoundaddr(void)
+{
+	Addr addr;
+	Addr *ap, *next;
+
+	addr.u.left = simpleaddr();
+	if((addr.type = cmdskipbl())!=',' && addr.type!=';')
+		return addr.u.left;
+	getch();
+	next = addr.next = compoundaddr();
+	if(next && (next->type==',' || next->type==';') && next->u.left==0)
+		editerror("bad address syntax");
+	ap = newaddr();
+	*ap = addr;
+	return ap;
+}
blob - /dev/null
blob + efa0b02e381fe5d8e212113cdcf1b3f8f14546ce (mode 644)
--- /dev/null
+++ src/cmd/acme/edit.h
@@ -0,0 +1,101 @@
+/*#pragma	varargck	argpos	editerror	1*/
+
+typedef struct Addr	Addr;
+typedef struct Address	Address;
+typedef struct Cmd	Cmd;
+typedef struct List	List;
+typedef struct String	String;
+
+struct String
+{
+	int	n;		/* excludes NUL */
+	Rune	*r;		/* includes NUL */
+	int	nalloc;
+};
+
+struct Addr
+{
+	char	type;	/* # (char addr), l (line addr), / ? . $ + - , ; */
+	union{
+		String	*re;
+		Addr	*left;		/* left side of , and ; */
+	} u;
+	ulong	num;
+	Addr	*next;			/* or right side of , and ; */
+};
+
+struct Address
+{
+	Range	r;
+	File	*f;
+};
+
+struct Cmd
+{
+	Addr	*addr;			/* address (range of text) */
+	String	*re;			/* regular expression for e.g. 'x' */
+	union{
+		Cmd	*cmd;		/* target of x, g, {, etc. */
+		String	*text;		/* text of a, c, i; rhs of s */
+		Addr	*mtaddr;		/* address for m, t */
+	} u;
+	Cmd	*next;			/* pointer to next element in {} */
+	short	num;
+	ushort	flag;			/* whatever */
+	ushort	cmdc;			/* command character; 'x' etc. */
+};
+
+extern struct cmdtab{
+	ushort	cmdc;		/* command character */
+	uchar	text;		/* takes a textual argument? */
+	uchar	regexp;		/* takes a regular expression? */
+	uchar	addr;		/* takes an address (m or t)? */
+	uchar	defcmd;		/* default command; 0==>none */
+	uchar	defaddr;	/* default address */
+	uchar	count;		/* takes a count e.g. s2/// */
+	char	*token;		/* takes text terminated by one of these */
+	int	(*fn)(Text*, Cmd*);	/* function to call with parse tree */
+}cmdtab[];
+
+#define	INCR	25	/* delta when growing list */
+
+struct List	/* code depends on a long being able to hold a pointer */
+{
+	int	nalloc;
+	int	nused;
+	union{
+		void	*listptr;
+		Block	*blkptr;
+		long	*longptr;
+		uchar*	*ucharptr;
+		String*	*stringptr;
+		File*	*fileptr;
+	} u;
+};
+
+enum Defaddr{	/* default addresses */
+	aNo,
+	aDot,
+	aAll,
+};
+
+int	nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*);
+int	c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*);
+int	B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*);
+int	f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*);
+int	k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*);
+int	p_cmd(Text*, Cmd*);
+int	s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*);
+int	x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*);
+int	eq_cmd(Text*, Cmd*);
+
+String	*allocstring(int);
+void		freestring(String*);
+String	*getregexp(int);
+Addr	*newaddr(void);
+Address	cmdaddress(Addr*, Address, int);
+int	cmdexec(Text*, Cmd*);
+void	editerror(char*, ...);
+int	cmdlookup(int);
+void	resetxec(void);
+void	Straddc(String*, int);
blob - /dev/null
blob + e86af6ec1de57bb8821fbfc96c1be9c4d10d961b (mode 644)
--- /dev/null
+++ src/cmd/acme/elog.c
@@ -0,0 +1,350 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+#include "edit.h"
+
+static char Wsequence[] = "warning: changes out of sequence\n";
+static int	warned = FALSE;
+
+/*
+ * Log of changes made by editing commands.  Three reasons for this:
+ * 1) We want addresses in commands to apply to old file, not file-in-change.
+ * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
+ * 3) This gives an opportunity to optimize by merging adjacent changes.
+ * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
+ * separate implementation.  To do this well, we use Replace as well as
+ * Insert and Delete
+ */
+
+typedef struct Buflog Buflog;
+struct Buflog
+{
+	short	type;		/* Replace, Filename */
+	uint		q0;		/* location of change (unused in f) */
+	uint		nd;		/* # runes to delete */
+	uint		nr;		/* # runes in string or file name */
+};
+
+enum
+{
+	Buflogsize = sizeof(Buflog)/sizeof(Rune),
+};
+
+/*
+ * Minstring shouldn't be very big or we will do lots of I/O for small changes.
+ * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
+ */
+enum
+{
+	Minstring = 16,		/* distance beneath which we merge changes */
+	Maxstring = RBUFSIZE,	/* maximum length of change we will merge into one */
+};
+
+void
+eloginit(File *f)
+{
+	if(f->elog.type != Empty)
+		return;
+	f->elog.type = Null;
+	if(f->elogbuf == nil)
+		f->elogbuf = emalloc(sizeof(Buffer));
+	if(f->elog.r == nil)
+		f->elog.r = fbufalloc();
+	bufreset(f->elogbuf);
+}
+
+void
+elogclose(File *f)
+{
+	if(f->elogbuf){
+		bufclose(f->elogbuf);
+		free(f->elogbuf);
+		f->elogbuf = nil;
+	}
+}
+
+void
+elogreset(File *f)
+{
+	f->elog.type = Null;
+	f->elog.nd = 0;
+	f->elog.nr = 0;
+}
+
+void
+elogterm(File *f)
+{
+	elogreset(f);
+	if(f->elogbuf)
+		bufreset(f->elogbuf);
+	f->elog.type = Empty;
+	fbuffree(f->elog.r);
+	f->elog.r = nil;
+	warned = FALSE;
+}
+
+void
+elogflush(File *f)
+{
+	Buflog b;
+
+	b.type = f->elog.type;
+	b.q0 = f->elog.q0;
+	b.nd = f->elog.nd;
+	b.nr = f->elog.nr;
+	switch(f->elog.type){
+	default:
+		warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
+		break;
+	case Null:
+		break;
+	case Insert:
+	case Replace:
+		if(f->elog.nr > 0)
+			bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
+		/* fall through */
+	case Delete:
+		bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
+		break;
+	}
+	elogreset(f);
+}
+
+void
+elogreplace(File *f, int q0, int q1, Rune *r, int nr)
+{
+	uint gap;
+
+	if(q0==q1 && nr==0)
+		return;
+	eloginit(f);
+	if(f->elog.type!=Null && q0<f->elog.q0){
+		if(warned++ == 0)
+			warning(nil, Wsequence);
+		elogflush(f);
+	}
+	/* try to merge with previous */
+	gap = q0 - (f->elog.q0+f->elog.nd);	/* gap between previous and this */
+	if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
+		if(gap < Minstring){
+			if(gap > 0){
+				bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
+				f->elog.nr += gap;
+			}
+			f->elog.nd += gap + q1-q0;
+			runemove(f->elog.r+f->elog.nr, r, nr);
+			f->elog.nr += nr;
+			return;
+		}
+	}
+	elogflush(f);
+	f->elog.type = Replace;
+	f->elog.q0 = q0;
+	f->elog.nd = q1-q0;
+	f->elog.nr = nr;
+	if(nr > RBUFSIZE)
+		editerror("internal error: replacement string too large(%d)", nr);
+	runemove(f->elog.r, r, nr);
+}
+
+void
+eloginsert(File *f, int q0, Rune *r, int nr)
+{
+	int n;
+
+	if(nr == 0)
+		return;
+	eloginit(f);
+	if(f->elog.type!=Null && q0<f->elog.q0){
+		if(warned++ == 0)
+			warning(nil, Wsequence);
+		elogflush(f);
+	}
+	/* try to merge with previous */
+	if(f->elog.type==Insert && q0==f->elog.q0 && (q0+nr)-f->elog.q0<Maxstring){
+		runemove(f->elog.r+f->elog.nr, r, nr);
+		f->elog.nr += nr;
+		return;
+	}
+	while(nr > 0){
+		elogflush(f);
+		f->elog.type = Insert;
+		f->elog.q0 = q0;
+		n = nr;
+		if(n > RBUFSIZE)
+			n = RBUFSIZE;
+		f->elog.nr = n;
+		runemove(f->elog.r, r, n);
+		r += n;
+		nr -= n;
+	}
+}
+
+void
+elogdelete(File *f, int q0, int q1)
+{
+	if(q0 == q1)
+		return;
+	eloginit(f);
+	if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
+		if(warned++ == 0)
+			warning(nil, Wsequence);
+		elogflush(f);
+	}
+	/* try to merge with previous */
+	if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
+		f->elog.nd += q1-q0;
+		return;
+	}
+	elogflush(f);
+	f->elog.type = Delete;
+	f->elog.q0 = q0;
+	f->elog.nd = q1-q0;
+}
+
+#define tracelog 0
+void
+elogapply(File *f)
+{
+	Buflog b;
+	Rune *buf;
+	uint i, n, up, mod;
+	uint q0, q1, tq0, tq1;
+	Buffer *log;
+	Text *t;
+
+	elogflush(f);
+	log = f->elogbuf;
+	t = f->curtext;
+
+	buf = fbufalloc();
+	mod = FALSE;
+
+	/*
+	 * The edit commands have already updated the selection in t->q0, t->q1.
+	 * (At least, they are supposed to have updated them.
+	 * We still keep finding commands that don't do it right.)
+	 * The textinsert and textdelete calls below will update it again, so save the
+	 * current setting and restore it at the end.
+	 */
+	q0 = t->q0;
+	q1 = t->q1;
+	/*
+	 * We constrain the addresses in here (with textconstrain()) because
+	 * overlapping changes will generate bogus addresses.   We will warn
+	 * about changes out of sequence but proceed anyway; here we must
+	 * keep things in range.
+	 */
+
+	while(log->nc > 0){
+		up = log->nc-Buflogsize;
+		bufread(log, up, (Rune*)&b, Buflogsize);
+		switch(b.type){
+		default:
+			fprint(2, "elogapply: 0x%ux\n", b.type);
+			abort();
+			break;
+
+		case Replace:
+			if(tracelog)
+				warning(nil, "elog replace %d %d\n",
+					b.q0, b.q0+b.nd);
+			if(!mod){
+				mod = TRUE;
+				filemark(f);
+			}
+			textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
+			textdelete(t, tq0, tq1, TRUE);
+			up -= b.nr;
+			for(i=0; i<b.nr; i+=n){
+				n = b.nr - i;
+				if(n > RBUFSIZE)
+					n = RBUFSIZE;
+				bufread(log, up+i, buf, n);
+				textinsert(t, tq0+i, buf, n, TRUE);
+			}
+			break;
+
+		case Delete:
+			if(tracelog)
+				warning(nil, "elog delete %d %d\n",
+					b.q0, b.q0+b.nd);
+			if(!mod){
+				mod = TRUE;
+				filemark(f);
+			}
+			textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
+			textdelete(t, tq0, tq1, TRUE);
+			break;
+
+		case Insert:
+			if(tracelog)
+				warning(nil, "elog insert %d %d\n",
+					b.q0, b.q0+b.nr);
+			if(!mod){
+				mod = TRUE;
+				filemark(f);
+			}
+			textconstrain(t, b.q0, b.q0, &tq0, &tq1);
+			up -= b.nr;
+			for(i=0; i<b.nr; i+=n){
+				n = b.nr - i;
+				if(n > RBUFSIZE)
+					n = RBUFSIZE;
+				bufread(log, up+i, buf, n);
+				textinsert(t, tq0+i, buf, n, TRUE);
+			}
+			break;
+
+/*		case Filename:
+			f->seq = u.seq;
+			fileunsetname(f, epsilon);
+			f->mod = u.mod;
+			up -= u.n;
+			free(f->name);
+			if(u.n == 0)
+				f->name = nil;
+			else
+				f->name = runemalloc(u.n);
+			bufread(delta, up, f->name, u.n);
+			f->nname = u.n;
+			break;
+*/
+		}
+		bufdelete(log, up, log->nc);
+	}
+	fbuffree(buf);
+	if(warned){
+		/*
+		 * Changes were out of order, so the q0 and q1
+		 * computed while generating those changes are not
+		 * to be trusted.
+		 */
+		q1 = min(q1, f->b.nc);
+		q0 = min(q0, q1);
+	}
+	elogterm(f);
+
+	/*
+	 * The q0 and q1 are supposed to be fine (see comment
+	 * above, where we saved them), but bad addresses
+	 * will cause bufload to crash, so double check.
+	 */
+	if(q0 > f->b.nc || q1 > f->b.nc || q0 > q1){
+		warning(nil, "elogapply: can't happen %d %d %d\n", q0, q1, f->b.nc);
+		q1 = min(q1, f->b.nc);
+		q0 = min(q0, q1);
+	}
+
+	t->q0 = q0;
+	t->q1 = q1;
+}
blob - /dev/null
blob + 9c29dc1ab737e3a1536a882d712df8287d399e01 (mode 644)
--- /dev/null
+++ src/cmd/acme/exec.c
@@ -0,0 +1,1491 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#define Fid FsFid
+#include <fs.h>
+#undef Fid
+#include "dat.h"
+#include "fns.h"
+
+Buffer	snarfbuf;
+
+void	del(Text*, Text*, Text*, int, int, Rune*, int);
+void	delcol(Text*, Text*, Text*, int, int, Rune*, int);
+void	dump(Text*, Text*, Text*, int, int, Rune*, int);
+void	edit(Text*, Text*, Text*, int, int, Rune*, int);
+void	xexit(Text*, Text*, Text*, int, int, Rune*, int);
+void	fontx(Text*, Text*, Text*, int, int, Rune*, int);
+void	get(Text*, Text*, Text*, int, int, Rune*, int);
+void	id(Text*, Text*, Text*, int, int, Rune*, int);
+void	incl(Text*, Text*, Text*, int, int, Rune*, int);
+void	xkill(Text*, Text*, Text*, int, int, Rune*, int);
+void	local(Text*, Text*, Text*, int, int, Rune*, int);
+void	look(Text*, Text*, Text*, int, int, Rune*, int);
+void	newcol(Text*, Text*, Text*, int, int, Rune*, int);
+void	paste(Text*, Text*, Text*, int, int, Rune*, int);
+void	put(Text*, Text*, Text*, int, int, Rune*, int);
+void	putall(Text*, Text*, Text*, int, int, Rune*, int);
+void	sendx(Text*, Text*, Text*, int, int, Rune*, int);
+void	sort(Text*, Text*, Text*, int, int, Rune*, int);
+void	tab(Text*, Text*, Text*, int, int, Rune*, int);
+void	zeroxx(Text*, Text*, Text*, int, int, Rune*, int);
+
+typedef struct Exectab Exectab;
+struct Exectab
+{
+	Rune	*name;
+	void	(*fn)(Text*, Text*, Text*, int, int, Rune*, int);
+	int		mark;
+	int		flag1;
+	int		flag2;
+};
+
+static Rune LCut[] = { 'C', 'u', 't', 0 };
+static Rune LDel[] = { 'D', 'e', 'l', 0 };
+static Rune LDelcol[] = { 'D', 'e', 'l', 'c', 'o', 'l', 0 };
+static Rune LDelete[] = { 'D', 'e', 'l', 'e', 't', 'e', 0 };
+static Rune LDump[] = { 'D', 'u', 'm', 'p', 0 };
+static Rune LEdit[] = { 'E', 'd', 'i', 't', 0 };
+static Rune LExit[] = { 'E', 'x', 'i', 't', 0 };
+static Rune LFont[] = { 'F', 'o', 'n', 't', 0 };
+static Rune LGet[] = { 'G', 'e', 't', 0 };
+static Rune LID[] = { 'I', 'D', 0 };
+static Rune LIncl[] = { 'I', 'n', 'c', 'l', 0 };
+static Rune LKill[] = { 'K', 'i', 'l', 'l', 0 };
+static Rune LLoad[] = { 'L', 'o', 'a', 'd', 0 };
+static Rune LLocal[] = { 'L', 'o', 'c', 'a', 'l', 0 };
+static Rune LLook[] = { 'L', 'o', 'o', 'k', 0 };
+static Rune LNew[] = { 'N', 'e', 'w', 0 };
+static Rune LNewcol[] = { 'N', 'e', 'w', 'c', 'o', 'l', 0 };
+static Rune LPaste[] = { 'P', 'a', 's', 't', 'e', 0 };
+static Rune LPut[] = { 'P', 'u', 't', 0 };
+static Rune LPutall[] = { 'P', 'u', 't', 'a', 'l', 'l', 0 };
+static Rune LRedo[] = { 'R', 'e', 'd', 'o', 0 };
+static Rune LSend[] = { 'S', 'e', 'n', 'd', 0 };
+static Rune LSnarf[] = { 'S', 'n', 'a', 'r', 'f', 0 };
+static Rune LSort[] = { 'S', 'o', 'r', 't', 0 };
+static Rune LTab[] = { 'T', 'a', 'b', 0 };
+static Rune LUndo[] = { 'U', 'n', 'd', 'o', 0 };
+static Rune LZerox[] = { 'Z', 'e', 'r', 'o', 'x', 0 };
+
+Exectab exectab[] = {
+	{ LCut,		cut,		TRUE,	TRUE,	TRUE	},
+	{ LDel,		del,		FALSE,	FALSE,	XXX		},
+	{ LDelcol,	delcol,	FALSE,	XXX,		XXX		},
+	{ LDelete,	del,		FALSE,	TRUE,	XXX		},
+	{ LDump,	dump,	FALSE,	TRUE,	XXX		},
+	{ LEdit,		edit,		FALSE,	XXX,		XXX		},
+	{ LExit,		xexit,		FALSE,	XXX,		XXX		},
+	{ LFont,		fontx,	FALSE,	XXX,		XXX		},
+	{ LGet,		get,		FALSE,	TRUE,	XXX		},
+	{ LID,		id,		FALSE,	XXX,		XXX		},
+	{ LIncl,		incl,		FALSE,	XXX,		XXX		},
+	{ LKill,		xkill,		FALSE,	XXX,		XXX		},
+	{ LLoad,		dump,	FALSE,	FALSE,	XXX		},
+	{ LLocal,		local,	FALSE,	XXX,		XXX		},
+	{ LLook,		look,		FALSE,	XXX,		XXX		},
+	{ LNew,		new,		FALSE,	XXX,		XXX		},
+	{ LNewcol,	newcol,	FALSE,	XXX,		XXX		},
+	{ LPaste,		paste,	TRUE,	TRUE,	XXX		},
+	{ LPut,		put,		FALSE,	XXX,		XXX		},
+	{ LPutall,		putall,	FALSE,	XXX,		XXX		},
+	{ LRedo,		undo,	FALSE,	FALSE,	XXX		},
+	{ LSend,		sendx,	TRUE,	XXX,		XXX		},
+	{ LSnarf,		cut,		FALSE,	TRUE,	FALSE	},
+	{ LSort,		sort,		FALSE,	XXX,		XXX		},
+	{ LTab,		tab,		FALSE,	XXX,		XXX		},
+	{ LUndo,		undo,	FALSE,	TRUE,	XXX		},
+	{ LZerox,	zeroxx,	FALSE,	XXX,		XXX		},
+	{ nil, 			nil,		0,		0,		0		},
+};
+
+Exectab*
+lookup(Rune *r, int n)
+{
+	Exectab *e;
+	int nr;
+
+	r = skipbl(r, n, &n);
+	if(n == 0)
+		return nil;
+	findbl(r, n, &nr);
+	nr = n-nr;
+	for(e=exectab; e->name; e++)
+		if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
+			return e;
+	return nil;
+}
+
+int
+isexecc(int c)
+{
+	if(isfilec(c))
+		return 1;
+	return c=='<' || c=='|' || c=='>';
+}
+
+void
+execute(Text *t, uint aq0, uint aq1, int external, Text *argt)
+{
+	uint q0, q1;
+	Rune *r, *s;
+	char *b, *a, *aa;
+	Exectab *e;
+	int c, n, f;
+	Runestr dir;
+
+	q0 = aq0;
+	q1 = aq1;
+	if(q1 == q0){	/* expand to find word (actually file name) */
+		/* if in selection, choose selection */
+		if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+			q0 = t->q0;
+			q1 = t->q1;
+		}else{
+			while(q1<t->file->b.nc && isexecc(c=textreadc(t, q1)) && c!=':')
+				q1++;
+			while(q0>0 && isexecc(c=textreadc(t, q0-1)) && c!=':')
+				q0--;
+			if(q1 == q0)
+				return;
+		}
+	}
+	r = runemalloc(q1-q0);
+	bufread(&t->file->b, q0, r, q1-q0);
+	e = lookup(r, q1-q0);
+	if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
+		f = 0;
+		if(e)
+			f |= 1;
+		if(q0!=aq0 || q1!=aq1){
+			bufread(&t->file->b, aq0, r, aq1-aq0);
+			f |= 2;
+		}
+		aa = getbytearg(argt, TRUE, TRUE, &a);
+		if(a){	
+			if(strlen(a) > EVENTSIZE){	/* too big; too bad */
+				free(aa);
+				free(a);
+				warning(nil, "`argument string too long\n");
+				return;
+			}
+			f |= 8;
+		}
+		c = 'x';
+		if(t->what == Body)
+			c = 'X';
+		n = aq1-aq0;
+		if(n <= EVENTSIZE)
+			winevent(t->w, "%c%d %d %d %d %.*S\n", c, aq0, aq1, f, n, n, r);
+		else
+			winevent(t->w, "%c%d %d %d 0 \n", c, aq0, aq1, f, n);
+		if(q0!=aq0 || q1!=aq1){
+			n = q1-q0;
+			bufread(&t->file->b, q0, r, n);
+			if(n <= EVENTSIZE)
+				winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r);
+			else
+				winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n);
+		}
+		if(a){
+			winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a);
+			if(aa)
+				winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa);
+			else
+				winevent(t->w, "%c0 0 0 0 \n", c);
+		}
+		free(r);
+		free(aa);
+		free(a);
+		return;
+	}
+	if(e){
+		if(e->mark && seltext!=nil)
+		if(seltext->what == Body){
+			seq++;
+			filemark(seltext->w->body.file);
+		}
+		s = skipbl(r, q1-q0, &n);
+		s = findbl(s, n, &n);
+		s = skipbl(s, n, &n);
+		(*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n);
+		free(r);
+		return;
+	}
+
+	b = runetobyte(r, q1-q0);
+	free(r);
+	dir = dirname(t, nil, 0);
+	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
+		free(dir.r);
+		dir.r = nil;
+		dir.nr = 0;
+	}
+	aa = getbytearg(argt, TRUE, TRUE, &a);
+	if(t->w)
+		incref(&t->w->ref);
+	run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE);
+}
+
+char*
+printarg(Text *argt, uint q0, uint q1)
+{
+	char *buf;
+
+	if(argt->what!=Body || argt->file->name==nil)
+		return nil;
+	buf = emalloc(argt->file->nname+32);
+	if(q0 == q1)
+		sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0);
+	else
+		sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1);
+	return buf;
+}
+
+char*
+getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp)
+{
+	int n;
+	Expand e;
+	char *a;
+
+	*rp = nil;
+	*nrp = 0;
+	if(argt == nil)
+		return nil;
+	a = nil;
+	textcommit(argt, TRUE);
+	if(expand(argt, argt->q0, argt->q1, &e)){
+		free(e.bname);
+		if(e.nname && dofile){
+			e.name = runerealloc(e.name, e.nname+1);
+			if(doaddr)
+				a = printarg(argt, e.q0, e.q1);
+			*rp = e.name;
+			*nrp = e.nname;
+			return a;
+		}
+		free(e.name);
+	}else{
+		e.q0 = argt->q0;
+		e.q1 = argt->q1;
+	}
+	n = e.q1 - e.q0;
+	*rp = runemalloc(n+1);
+	bufread(&argt->file->b, e.q0, *rp, n);
+	if(doaddr)
+		a = printarg(argt, e.q0, e.q1);
+	*nrp = n;
+	return a;
+}
+
+char*
+getbytearg(Text *argt, int doaddr, int dofile, char **bp)
+{
+	Rune *r;
+	int n;
+	char *aa;
+
+	*bp = nil;
+	aa = getarg(argt, doaddr, dofile, &r, &n);
+	if(r == nil)
+		return nil;
+	*bp = runetobyte(r, n);
+	free(r);
+	return aa;
+}
+
+void
+newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	Column *c;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	c = rowadd(et->row, nil, -1);
+	if(c)
+		winsettag(coladd(c, nil, nil, -1));
+}
+
+void
+delcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	int i;
+	Column *c;
+	Window *w;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	c = et->col;
+	if(c==nil || colclean(c)==0)
+		return;
+	for(i=0; i<c->nw; i++){
+		w = c->w[i];
+		if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata] > 0){
+			warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name);
+			return;
+		}
+	}
+	rowclose(et->col->row, et->col, TRUE);
+}
+
+void
+del(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	if(et->col==nil || et->w == nil)
+		return;
+	if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE))
+		colclose(et->col, et->w, TRUE);
+}
+
+void
+sort(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(et->col)
+		colsort(et->col);
+}
+
+uint
+seqof(Window *w, int isundo)
+{
+	/* if it's undo, see who changed with us */
+	if(isundo)
+		return w->body.file->seq;
+	/* if it's redo, see who we'll be sync'ed up with */
+	return fileredoseq(w->body.file);
+}
+
+void
+undo(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
+{
+	int i, j;
+	Column *c;
+	Window *w;
+	uint seq;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	if(et==nil || et->w== nil)
+		return;
+	seq = seqof(et->w, flag1);
+	if(seq == 0){
+		/* nothing to undo */
+		return;
+	}
+	/*
+	 * Undo the executing window first. Its display will update. other windows
+	 * in the same file will not call show() and jump to a different location in the file.
+	 * Simultaneous changes to other files will be chaotic, however.
+	 */
+	winundo(et->w, flag1);
+	for(i=0; i<row.ncol; i++){
+		c = row.col[i];
+		for(j=0; j<c->nw; j++){
+			w = c->w[j];
+			if(w == et->w)
+				continue;
+			if(seqof(w, flag1) == seq)
+				winundo(w, flag1);
+		}
+	}
+}
+
+char*
+getname(Text *t, Text *argt, Rune *arg, int narg, int isput)
+{
+	char *s;
+	Rune *r;
+	int i, n, promote;
+	Runestr dir;
+
+	getarg(argt, FALSE, TRUE, &r, &n);
+	promote = FALSE;
+	if(r == nil)
+		promote = TRUE;
+	else if(isput){
+		/* if are doing a Put, want to synthesize name even for non-existent file */
+		/* best guess is that file name doesn't contain a slash */
+		promote = TRUE;
+		for(i=0; i<n; i++)
+			if(r[i] == '/'){
+				promote = FALSE;
+				break;
+			}
+		if(promote){
+			t = argt;
+			arg = r;
+			narg = n;
+		}
+	}
+	if(promote){
+		n = narg;
+		if(n <= 0){
+			s = runetobyte(t->file->name, t->file->nname);
+			return s;
+		}
+		/* prefix with directory name if necessary */
+		dir.r = nil;
+		dir.nr = 0;
+		if(n>0 && arg[0]!='/'){
+			dir = dirname(t, nil, 0);
+			if(n==1 && dir.r[0]=='.'){	/* sigh */
+				free(dir.r);
+				dir.r = nil;
+				dir.nr = 0;
+			}
+		}
+		if(dir.r){
+			r = runemalloc(dir.nr+n+1);
+			runemove(r, dir.r, dir.nr);
+			free(dir.r);
+			runemove(r+dir.nr, arg, n);
+			n += dir.nr;
+		}else{
+			r = runemalloc(n+1);
+			runemove(r, arg, n);
+		}
+	}
+	s = runetobyte(r, n);
+	free(r);
+	if(strlen(s) == 0){
+		free(s);
+		s = nil;
+	}
+	return s;
+}
+
+void
+zeroxx(Text *et, Text *t, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	Window *nw;
+	int c, locked;
+
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	locked = FALSE;
+	if(t!=nil && t->w!=nil && t->w!=et->w){
+		locked = TRUE;
+		c = 'M';
+		if(et->w)
+			c = et->w->owner;
+		winlock(t->w, c);
+	}
+	if(t == nil)
+		t = et;
+	if(t==nil || t->w==nil)
+		return;
+	t = &t->w->body;
+	if(t->w->isdir)
+		warning(nil, "%.*S is a directory; Zerox illegal\n", t->file->nname, t->file->name);
+	else{
+		nw = coladd(t->w->col, nil, t->w, -1);
+		/* ugly: fix locks so w->unlock works */
+		winlock1(nw, t->w->owner);
+	}
+	if(locked)
+		winunlock(t->w);
+}
+
+void
+get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg)
+{
+	char *name;
+	Rune *r;
+	int i, n, dirty, samename, isdir;
+	Window *w;
+	Text *u;
+	Dir *d;
+
+	USED(_0);
+
+	if(flag1)
+		if(et==nil || et->w==nil)
+			return;
+	if(!et->w->isdir && (et->w->body.file->b.nc>0 && !winclean(et->w, TRUE)))
+		return;
+	w = et->w;
+	t = &w->body;
+	name = getname(t, argt, arg, narg, FALSE);
+	if(name == nil){
+		warning(nil, "no file name\n");
+		return;
+	}
+	if(t->file->ntext>1){
+		d = dirstat(name);
+		isdir = (d!=nil && (d->qid.type & QTDIR));
+		free(d);
+		if(isdir)
+			warning(nil, "%s is a directory; can't read with multiple windows on it\n", name);
+		return;
+	}
+	r = bytetorune(name, &n);
+	for(i=0; i<t->file->ntext; i++){
+		u = t->file->text[i];
+		/* second and subsequent calls with zero an already empty buffer, but OK */
+		textreset(u);
+		windirfree(u->w);
+	}
+	samename = runeeq(r, n, t->file->name, t->file->nname);
+	textload(t, 0, name, samename);
+	if(samename){
+		t->file->mod = FALSE;
+		dirty = FALSE;
+	}else{
+		t->file->mod = TRUE;
+		dirty = TRUE;
+	}
+	for(i=0; i<t->file->ntext; i++)
+		t->file->text[i]->w->dirty = dirty;
+	free(name);
+	free(r);
+	winsettag(w);
+	t->file->unread = FALSE;
+	for(i=0; i<t->file->ntext; i++){
+		u = t->file->text[i];
+		textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
+		textscrdraw(u);
+	}
+}
+
+void
+putfile(File *f, int q0, int q1, Rune *namer, int nname)
+{
+	uint n, m;
+	Rune *r;
+	char *s, *name;
+	int i, fd, q;
+	Dir *d, *d1;
+	Window *w;
+	int isapp;
+
+	w = f->curtext->w;
+	name = runetobyte(namer, nname);
+	d = dirstat(name);
+	if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
+		/* f->mtime+1 because when talking over NFS it's often off by a second */
+		if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime+1<d->mtime){
+			f->dev = d->dev;
+			f->qidpath = d->qid.path;
+			f->mtime = d->mtime;
+			if(f->unread)
+				warningew(w, nil, "%s not written; file already exists\n", name);
+			else
+				warningew(w, nil, "%s modified%s%s since last read\n", name, d->muid[0]?" by ":"", d->muid);
+			goto Rescue1;
+		}
+	}
+	fd = create(name, OWRITE, 0666);
+	if(fd < 0){
+		warningew(w, nil, "can't create file %s: %r\n", name);
+		goto Rescue1;
+	}
+	r = fbufalloc();
+	s = fbufalloc();
+	free(d);
+	d = dirfstat(fd);
+	isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
+	if(isapp){
+		warningew(w, nil, "%s not written; file is append only\n", name);
+		goto Rescue2;
+	}
+
+	for(q=q0; q<q1; q+=n){
+		n = q1 - q;
+		if(n > BUFSIZE/UTFmax)
+			n = BUFSIZE/UTFmax;
+		bufread(&f->b, q, r, n);
+		m = snprint(s, BUFSIZE+1, "%.*S", n, r);
+		if(write(fd, s, m) != m){
+			warningew(w, nil, "can't write file %s: %r\n", name);
+			goto Rescue2;
+		}
+	}
+	if(runeeq(namer, nname, f->name, f->nname)){
+		if(q0!=0 || q1!=f->b.nc){
+			f->mod = TRUE;
+			w->dirty = TRUE;
+			f->unread = TRUE;
+		}else{
+			d1 = dirfstat(fd);
+			if(d1 != nil){
+				free(d);
+				d = d1;
+			}
+			f->qidpath = d->qid.path;
+			f->dev = d->dev;
+			f->mtime = d->mtime;
+			f->mod = FALSE;
+			w->dirty = FALSE;
+			f->unread = FALSE;
+		}
+		for(i=0; i<f->ntext; i++){
+			f->text[i]->w->putseq = f->seq;
+			f->text[i]->w->dirty = w->dirty;
+		}
+	}
+	fbuffree(s);
+	fbuffree(r);
+	free(d);
+	free(namer);
+	free(name);
+	close(fd);
+	winsettag(w);
+	return;
+
+    Rescue2:
+	fbuffree(s);
+	fbuffree(r);
+	close(fd);
+	/* fall through */
+
+    Rescue1:
+	free(d);
+	free(namer);
+	free(name);
+}
+
+void
+put(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	int nname;
+	Rune  *namer;
+	Window *w;
+	File *f;
+	char *name;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(et==nil || et->w==nil || et->w->isdir)
+		return;
+	w = et->w;
+	f = w->body.file;
+	name = getname(&w->body, argt, arg, narg, TRUE);
+	if(name == nil){
+		warningew(w, nil, "no file name\n");
+		return;
+	}
+	namer = bytetorune(name, &nname);
+	putfile(f, 0, f->b.nc, namer, nname);
+	free(name);
+}
+
+void
+dump(Text *_0, Text *_1, Text *argt, int isdump, int _2, Rune *arg, int narg)
+{
+	char *name;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(narg)
+		name = runetobyte(arg, narg);
+	else
+		getbytearg(argt, FALSE, TRUE, &name);
+	if(isdump)
+		rowdump(&row, name);
+	else
+		rowload(&row, name, FALSE);
+	free(name);
+}
+
+void
+cut(Text *et, Text *t, Text *_0, int dosnarf, int docut, Rune *_2, int _3)
+{
+	uint q0, q1, n, locked, c;
+	Rune *r;
+
+	USED(_0);
+	USED(_2);
+	USED(_3);
+
+	/* use current window if snarfing and its selection is non-null */
+	if(et!=t && dosnarf && et->w!=nil){
+		if(et->w->body.q1>et->w->body.q0){
+			t = &et->w->body;
+			if(docut)
+				filemark(t->file);	/* seq has been incremented by execute */
+		}else if(et->w->tag.q1>et->w->tag.q0)
+			t = &et->w->tag;
+	}
+	if(t == nil){
+		/* can only happen if seltext == nil */
+		return;
+	}
+	locked = FALSE;
+	if(t->w!=nil && et->w!=t->w){
+		locked = TRUE;
+		c = 'M';
+		if(et->w)
+			c = et->w->owner;
+		winlock(t->w, c);
+	}
+	if(t->q0 == t->q1){
+		if(locked)
+			winunlock(t->w);
+		return;
+	}
+	if(dosnarf){
+		q0 = t->q0;
+		q1 = t->q1;
+		bufdelete(&snarfbuf, 0, snarfbuf.nc);
+		r = fbufalloc();
+		while(q0 < q1){
+			n = q1 - q0;
+			if(n > RBUFSIZE)
+				n = RBUFSIZE;
+			bufread(&t->file->b, q0, r, n);
+			bufinsert(&snarfbuf, snarfbuf.nc, r, n);
+			q0 += n;
+		}
+		fbuffree(r);
+		acmeputsnarf();
+	}
+	if(docut){
+		textdelete(t, t->q0, t->q1, TRUE);
+		textsetselect(t, t->q0, t->q0);
+		if(t->w){
+			textscrdraw(t);
+			winsettag(t->w);
+		}
+	}else if(dosnarf)	/* Snarf command */
+		argtext = t;
+	if(locked)
+		winunlock(t->w);
+}
+
+void
+paste(Text *et, Text *t, Text *_0, int selectall, int tobody, Rune *_1, int _2)
+{
+	int c;
+	uint q, q0, q1, n;
+	Rune *r;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	/* if(tobody), use body of executing window  (Paste or Send command) */
+	if(tobody && et!=nil && et->w!=nil){
+		t = &et->w->body;
+		filemark(t->file);	/* seq has been incremented by execute */
+	}
+	if(t == nil)
+		return;
+
+	acmegetsnarf();
+	if(t==nil || snarfbuf.nc==0)
+		return;
+	if(t->w!=nil && et->w!=t->w){
+		c = 'M';
+		if(et->w)
+			c = et->w->owner;
+		winlock(t->w, c);
+	}
+	cut(t, t, nil, FALSE, TRUE, nil, 0);
+	q = 0;
+	q0 = t->q0;
+	q1 = t->q0+snarfbuf.nc;
+	r = fbufalloc();
+	while(q0 < q1){
+		n = q1 - q0;
+		if(n > RBUFSIZE)
+			n = RBUFSIZE;
+		if(r == nil)
+			r = runemalloc(n);
+		bufread(&snarfbuf, q, r, n);
+		textinsert(t, q0, r, n, TRUE);
+		q += n;
+		q0 += n;
+	}
+	fbuffree(r);
+	if(selectall)
+		textsetselect(t, t->q0, q1);
+	else
+		textsetselect(t, q1, q1);
+	if(t->w){
+		textscrdraw(t);
+		winsettag(t->w);
+	}
+	if(t->w!=nil && et->w!=t->w)
+		winunlock(t->w);
+}
+
+void
+look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
+{
+	Rune *r;
+	int n;
+
+	USED(_0);
+	USED(_1);
+
+	if(et && et->w){
+		t = &et->w->body;
+		if(narg > 0){
+			search(t, arg, narg);
+			return;
+		}
+		getarg(argt, FALSE, FALSE, &r, &n);
+		if(r == nil){
+			n = t->q1-t->q0;
+			r = runemalloc(n);
+			bufread(&t->file->b, t->q0, r, n);
+		}
+		search(t, r, n);
+		free(r);
+	}
+}
+
+static Rune Lnl[] = { '\n', 0 };
+
+void
+sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	if(et->w==nil)
+		return;
+	t = &et->w->body;
+	if(t->q0 != t->q1)
+		cut(t, t, nil, TRUE, FALSE, nil, 0);
+	textsetselect(t, t->file->b.nc, t->file->b.nc);
+	paste(t, t, nil, TRUE, TRUE, nil, 0);
+	if(textreadc(t, t->file->b.nc-1) != '\n'){
+		textinsert(t, t->file->b.nc, Lnl, 1, TRUE);
+		textsetselect(t, t->file->b.nc, t->file->b.nc);
+	}
+}
+
+void
+edit(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *r;
+	int len;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(et == nil)
+		return;
+	getarg(argt, FALSE, TRUE, &r, &len);
+	seq++;
+	if(r != nil){
+		editcmd(et, r, len);
+		free(r);
+	}else
+		editcmd(et, arg, narg);
+}
+
+void
+xexit(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(et);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(rowclean(&row)){
+		sendul(cexit, 0);
+		threadexits(nil);
+	}
+}
+
+void
+putall(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	int i, j, e;
+	Window *w;
+	Column *c;
+	char *a;
+
+	USED(et);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	for(i=0; i<row.ncol; i++){
+		c = row.col[i];
+		for(j=0; j<c->nw; j++){
+			w = c->w[j];
+			if(w->isscratch || w->isdir || w->body.file->nname==0)
+				continue;
+			if(w->nopen[QWevent] > 0)
+				continue;
+			a = runetobyte(w->body.file->name, w->body.file->nname);
+			e = access(a, 0);
+			if(w->body.file->mod || w->body.ncache)
+				if(e < 0)
+					warning(nil, "no auto-Put of %s: %r\n", a);
+				else{
+					wincommit(w, &w->body);
+					put(&w->body, nil, nil, XXX, XXX, nil, 0);
+				}
+			free(a);
+		}
+	}
+}
+
+
+void
+id(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(et);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(et && et->w)
+		warning(nil, "/mnt/acme/%d/\n", et->w->id);
+}
+
+void
+local(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	char *a, *aa;
+	Runestr dir;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	aa = getbytearg(argt, TRUE, TRUE, &a);
+
+	dir = dirname(et, nil, 0);
+	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
+		free(dir.r);
+		dir.r = nil;
+		dir.nr = 0;
+	}
+	run(nil, runetobyte(arg, narg), dir.r, dir.nr, FALSE, aa, a, FALSE);
+}
+
+void
+xkill(Text *_0, Text *_1, Text *argt, int _2, int _3, Rune *arg, int narg)
+{
+	Rune *a, *cmd, *r;
+	int na;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+
+	getarg(argt, FALSE, FALSE, &r, &na);
+	if(r)
+		xkill(nil, nil, nil, 0, 0, r, na);
+	/* loop condition: *arg is not a blank */
+	for(;;){
+		a = findbl(arg, narg, &na);
+		if(a == arg)
+			break;
+		cmd = runemalloc(narg-na+1);
+		runemove(cmd, arg, narg-na);
+		sendp(ckill, cmd);
+		arg = skipbl(a, na, &narg);
+	}
+}
+
+static Rune Lfix[] = { 'f', 'i', 'x', 0 };
+static Rune Lvar[] = { 'v', 'a', 'r', 0 };
+
+void
+fontx(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
+{
+	Rune *a, *r, *flag, *file;
+	int na, nf;
+	char *aa;
+	Reffont *newfont;
+	Dirlist *dp;
+	int i, fix;
+
+	USED(_0);
+	USED(_1);
+
+	if(et==nil || et->w==nil)
+		return;
+	t = &et->w->body;
+	flag = nil;
+	file = nil;
+	/* loop condition: *arg is not a blank */
+	nf = 0;
+	for(;;){
+		a = findbl(arg, narg, &na);
+		if(a == arg)
+			break;
+		r = runemalloc(narg-na+1);
+		runemove(r, arg, narg-na);
+		if(runeeq(r, narg-na, Lfix, 3) || runeeq(r, narg-na, Lvar, 3)){
+			free(flag);
+			flag = r;
+		}else{
+			free(file);
+			file = r;
+			nf = narg-na;
+		}
+		arg = skipbl(a, na, &narg);
+	}
+	getarg(argt, FALSE, TRUE, &r, &na);
+	if(r)
+		if(runeeq(r, na, Lfix, 3) || runeeq(r, na, Lvar, 3)){
+			free(flag);
+			flag = r;
+		}else{
+			free(file);
+			file = r;
+			nf = na;
+		}
+	fix = 1;
+	if(flag)
+		fix = runeeq(flag, runestrlen(flag), Lfix, 3);
+	else if(file == nil){
+		newfont = rfget(FALSE, FALSE, FALSE, nil);
+		if(newfont)
+			fix = strcmp(newfont->f->name, t->fr.font->name)==0;
+	}
+	if(file){
+		aa = runetobyte(file, nf);
+		newfont = rfget(fix, flag!=nil, FALSE, aa);
+		free(aa);
+	}else
+		newfont = rfget(fix, FALSE, FALSE, nil);
+	if(newfont){
+		draw(screen, t->w->r, textcols[BACK], nil, ZP);
+		rfclose(t->reffont);
+		t->reffont = newfont;
+		t->fr.font = newfont->f;
+		frinittick(&t->fr);
+		if(t->w->isdir){
+			t->all.min.x++;	/* force recolumnation; disgusting! */
+			for(i=0; i<t->w->ndl; i++){
+				dp = t->w->dlp[i];
+				aa = runetobyte(dp->r, dp->nr);
+				dp->wid = stringwidth(newfont->f, aa);
+				free(aa);
+			}
+		}
+		/* avoid shrinking of window due to quantization */
+		colgrow(t->w->col, t->w, -1);
+	}
+	free(file);
+	free(flag);
+}
+
+void
+incl(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, n, len;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(et==nil || et->w==nil)
+		return;
+	w = et->w;
+	n = 0;
+	getarg(argt, FALSE, TRUE, &r, &len);
+	if(r){
+		n++;
+		winaddincl(w, r, len);
+	}
+	/* loop condition: *arg is not a blank */
+	for(;;){
+		a = findbl(arg, narg, &na);
+		if(a == arg)
+			break;
+		r = runemalloc(narg-na+1);
+		runemove(r, arg, narg-na);
+		n++;
+		winaddincl(w, r, narg-na);
+		arg = skipbl(a, na, &narg);
+	}
+	if(n==0 && w->nincl){
+		for(n=w->nincl; --n>=0; )
+			warning(nil, "%S ", w->incl[n]);
+		warning(nil, "\n");
+	}
+}
+
+void
+tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, len, tab;
+	char *p;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(et==nil || et->w==nil)
+		return;
+	w = et->w;
+	getarg(argt, FALSE, TRUE, &r, &len);
+	tab = 0;
+	if(r!=nil && len>0){
+		p = runetobyte(r, len);
+		if('0'<=p[0] && p[0]<='9')
+			tab = atoi(p);
+		free(p);
+	}else{
+		a = findbl(arg, narg, &na);
+		if(a != arg){
+			p = runetobyte(arg, narg-na);
+			if('0'<=p[0] && p[0]<='9')
+				tab = atoi(p);
+			free(p);
+		}
+	}
+	if(tab > 0){
+		if(w->body.tabstop != tab){
+			w->body.tabstop = tab;
+			winresize(w, w->r, 1);
+		}
+	}else
+		warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop);
+}
+
+void
+runproc(void *argvp)
+{
+	/* args: */
+		Window *win;
+		char *s;
+		Rune *rdir;
+		int ndir;
+		int newns;
+		char *argaddr;
+		char *arg;
+		Command *c;
+		Channel *cpid;
+		int iseditcmd;
+	/* end of args */
+	char *e, *t, *name, *filename, *dir, **av, *news;
+	Rune r, **incl;
+	int ac, w, inarg, i, n, fd, nincl, winid;
+	int sfd[3];
+	int pipechar;
+	char buf[512];
+	//static void *parg[2];
+	void **argv;
+	Fsys *fs;
+
+	argv = argvp;
+	win = argv[0];
+	s = argv[1];
+	rdir = argv[2];
+	ndir = (int)argv[3];
+	newns = (int)argv[4];
+	argaddr = argv[5];
+	arg = argv[6];
+	c = argv[7];
+	cpid = argv[8];
+	iseditcmd = (int)argv[9];
+	free(argv);
+
+	t = s;
+	while(*t==' ' || *t=='\n' || *t=='\t')
+		t++;
+	for(e=t; *e; e++)
+		if(*e==' ' || *e=='\n' || *e=='\t' )
+			break;
+	name = emalloc((e-t)+2);
+	memmove(name, t, e-t);
+	name[e-t] = 0;
+	e = utfrrune(name, '/');
+	if(e)
+		strcpy(name, e+1);
+	strcat(name, " ");	/* add blank here for ease in waittask */
+	c->name = bytetorune(name, &c->nname);
+	free(name);
+	pipechar = 0;
+	if(*t=='<' || *t=='|' || *t=='>')
+		pipechar = *t++;
+	c->iseditcmd = iseditcmd;
+	c->text = s;
+	if(rdir != nil){
+		dir = runetobyte(rdir, ndir);
+		chdir(dir);	/* ignore error: probably app. window */
+		free(dir);
+	}
+	if(newns){
+		nincl = 0;
+		incl = nil;
+		if(win){
+			filename = smprint("%.*S", win->body.file->nname, win->body.file->name);
+			nincl = win->nincl;
+			if(nincl > 0){
+				incl = emalloc(nincl*sizeof(Rune*));
+				for(i=0; i<nincl; i++){
+					n = runestrlen(win->incl[i]);
+					incl[i] = runemalloc(n+1);
+					runemove(incl[i], win->incl[i], n);
+				}
+			}
+			winid = win->id;
+		}else{
+			filename = nil;
+			winid = 0;
+			if(activewin)
+				winid = activewin->id;
+		}
+		rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
+		sprint(buf, "%d", winid);
+		putenv("winid", buf);
+
+		if(filename){
+			putenv("%", filename);
+			free(filename);
+		}
+		c->md = fsysmount(rdir, ndir, incl, nincl);
+		if(c->md == nil){
+			fprint(2, "child: can't allocate mntdir: %r\n");
+			threadexits("fsysmount");
+		}
+		sprint(buf, "%d", c->md->id);
+		if((fs = nsmount("acme", buf)) == nil){
+			fprint(2, "child: can't mount acme: %r\n");
+			fsysdelid(c->md);
+			c->md = nil;
+			threadexits("nsmount");
+		}
+		if(winid>0 && (pipechar=='|' || pipechar=='>')){
+			sprint(buf, "%d/rdsel", winid);
+			sfd[0] = fsopenfd(fs, buf, OREAD);
+		}else
+			sfd[0] = open("/dev/null", OREAD);
+		if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
+			if(iseditcmd){
+				if(winid > 0)
+					sprint(buf, "%d/editout", winid);
+				else
+					sprint(buf, "editout");
+			}else
+				sprint(buf, "%d/wrsel", winid);
+			sfd[1] = fsopenfd(fs, buf, OWRITE);
+			sfd[2] = fsopenfd(fs, "cons", OWRITE);
+		}else{
+			sfd[1] = fsopenfd(fs, "cons", OWRITE);
+			sfd[2] = sfd[1];
+		}
+		fsunmount(fs);
+	}else{
+		rfork(RFFDG|RFNOTEG);
+		fsysclose();
+		sfd[0] = open("/dev/null", OREAD);
+		sfd[1] = open("/dev/null", OWRITE);
+		sfd[2] = dup(erroutfd, -1);
+	}
+	if(win)
+		winclose(win);
+
+	if(argaddr)
+		putenv("acmeaddr", argaddr);
+	if(strlen(t) > sizeof buf-10)	/* may need to print into stack */
+		goto Hard;
+	inarg = FALSE;
+	for(e=t; *e; e+=w){
+		w = chartorune(&r, e);
+		if(r==' ' || r=='\t')
+			continue;
+		if(r < ' ')
+			goto Hard;
+		if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r))
+			goto Hard;
+		inarg = TRUE;
+	}
+	if(!inarg)
+		goto Fail;
+
+	ac = 0;
+	av = nil;
+	inarg = FALSE;
+	for(e=t; *e; e+=w){
+		w = chartorune(&r, e);
+		if(r==' ' || r=='\t'){
+			inarg = FALSE;
+			*e = 0;
+			continue;
+		}
+		if(!inarg){
+			inarg = TRUE;
+			av = realloc(av, (ac+1)*sizeof(char**));
+			av[ac++] = e;
+		}
+	}
+	av = realloc(av, (ac+2)*sizeof(char**));
+	av[ac++] = arg;
+	av[ac] = nil;
+	c->av = av;
+	procexec(cpid, sfd, av[0], av);
+/* libthread uses execvp so no need to do this */
+#if 0
+	e = av[0];
+	if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
+		goto Fail;
+	if(cputype){
+		sprint(buf, "%s/%s", cputype, av[0]);
+		procexec(cpid, sfd, buf, av);
+	}
+	sprint(buf, "/bin/%s", av[0]);
+	procexec(cpid, sfd, buf, av);
+#endif
+	goto Fail;
+
+Hard:
+
+	/*
+	 * ugly: set path = (. $cputype /bin)
+	 * should honor $path if unusual.
+	 */
+	if(cputype){
+		n = 0;
+		memmove(buf+n, ".", 2);
+		n += 2;
+		i = strlen(cputype)+1;
+		memmove(buf+n, cputype, i);
+		n += i;
+		memmove(buf+n, "/bin", 5);
+		n += 5;
+		fd = create("/env/path", OWRITE, 0666);
+		write(fd, buf, n);
+		close(fd);
+	}
+
+	if(arg){
+		news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1);
+		if(news){
+			sprint(news, "%s '%s'", t, arg);	/* BUG: what if quote in arg? */
+			free(s);
+			t = news;
+			c->text = news;
+		}
+	}
+	procexecl(cpid, sfd, "rc", "rc", "-c", t, nil);
+
+   Fail:
+	/* procexec hasn't happened, so send a zero */
+	close(sfd[0]);
+	close(sfd[1]);
+	if(sfd[2] != sfd[1])
+		close(sfd[2]);
+	sendul(cpid, 0);
+	threadexits(nil);
+}
+
+void
+runwaittask(void *v)
+{
+	Command *c;
+	Channel *cpid;
+	void **a;
+
+	threadsetname("runwaittask");
+	a = v;
+	c = a[0];
+	cpid = a[1];
+	free(a);
+	do
+		c->pid = recvul(cpid);
+	while(c->pid == ~0);
+	free(c->av);
+	if(c->pid != 0)	/* successful exec */
+		sendp(ccommand, c);
+	else{
+		if(c->iseditcmd)
+			sendul(cedit, 0);
+		free(c->name);
+		free(c->text);
+		free(c);
+	}
+	chanfree(cpid);
+}
+
+void
+run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd)
+{
+	void **arg;
+	Command *c;
+	Channel *cpid;
+
+	if(s == nil)
+		return;
+
+	arg = emalloc(10*sizeof(void*));
+	c = emalloc(sizeof *c);
+	cpid = chancreate(sizeof(ulong), 0);
+	arg[0] = win;
+	arg[1] = s;
+	arg[2] = rdir;
+	arg[3] = (void*)ndir;
+	arg[4] = (void*)newns;
+	arg[5] = argaddr;
+	arg[6] = xarg;
+	arg[7] = c;
+	arg[8] = cpid;
+	arg[9] = (void*)iseditcmd;
+	proccreate(runproc, arg, STACK);
+	/* mustn't block here because must be ready to answer mount() call in run() */
+	arg = emalloc(2*sizeof(void*));
+	arg[0] = c;
+	arg[1] = cpid;
+	threadcreate(runwaittask, arg, STACK);
+}
blob - /dev/null
blob + cca91b2ee7c7b5d6bf7d331fa88b3858b3cd3ed2 (mode 644)
--- /dev/null
+++ src/cmd/acme/file.c
@@ -0,0 +1,310 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+/*
+ * Structure of Undo list:
+ * 	The Undo structure follows any associated data, so the list
+ *	can be read backwards: read the structure, then read whatever
+ *	data is associated (insert string, file name) and precedes it.
+ *	The structure includes the previous value of the modify bit
+ *	and a sequence number; successive Undo structures with the
+ *	same sequence number represent simultaneous changes.
+ */
+
+typedef struct Undo Undo;
+struct Undo
+{
+	short	type;		/* Delete, Insert, Filename */
+	short	mod;	/* modify bit */
+	uint		seq;		/* sequence number */
+	uint		p0;		/* location of change (unused in f) */
+	uint		n;		/* # runes in string or file name */
+};
+
+enum
+{
+	Undosize = sizeof(Undo)/sizeof(Rune),
+};
+
+File*
+fileaddtext(File *f, Text *t)
+{
+	if(f == nil){
+		f = emalloc(sizeof(File));
+		f->unread = TRUE;
+	}
+	f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
+	f->text[f->ntext++] = t;
+	f->curtext = t;
+	return f;
+}
+
+void
+filedeltext(File *f, Text *t)
+{
+	int i;
+
+	for(i=0; i<f->ntext; i++)
+		if(f->text[i] == t)
+			goto Found;
+	error("can't find text in filedeltext");
+
+    Found:
+	f->ntext--;
+	if(f->ntext == 0){
+		fileclose(f);
+		return;
+	}
+	memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
+	if(f->curtext == t)
+		f->curtext = f->text[0];
+}
+
+void
+fileinsert(File *f, uint p0, Rune *s, uint ns)
+{
+	if(p0 > f->b.nc)
+		error("internal error: fileinsert");
+	if(f->seq > 0)
+		fileuninsert(f, &f->delta, p0, ns);
+	bufinsert(&f->b, p0, s, ns);
+	if(ns)
+		f->mod = TRUE;
+}
+
+void
+fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
+{
+	Undo u;
+
+	/* undo an insertion by deleting */
+	u.type = Delete;
+	u.mod = f->mod;
+	u.seq = f->seq;
+	u.p0 = p0;
+	u.n = ns;
+	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+filedelete(File *f, uint p0, uint p1)
+{
+	if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc))
+		error("internal error: filedelete");
+	if(f->seq > 0)
+		fileundelete(f, &f->delta, p0, p1);
+	bufdelete(&f->b, p0, p1);
+	if(p1 > p0)
+		f->mod = TRUE;
+}
+
+void
+fileundelete(File *f, Buffer *delta, uint p0, uint p1)
+{
+	Undo u;
+	Rune *buf;
+	uint i, n;
+
+	/* undo a deletion by inserting */
+	u.type = Insert;
+	u.mod = f->mod;
+	u.seq = f->seq;
+	u.p0 = p0;
+	u.n = p1-p0;
+	buf = fbufalloc();
+	for(i=p0; i<p1; i+=n){
+		n = p1 - i;
+		if(n > RBUFSIZE)
+			n = RBUFSIZE;
+		bufread(&f->b, i, buf, n);
+		bufinsert(delta, delta->nc, buf, n);
+	}
+	fbuffree(buf);
+	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+
+}
+
+void
+filesetname(File *f, Rune *name, int n)
+{
+	if(f->seq > 0)
+		fileunsetname(f, &f->delta);
+	free(f->name);
+	f->name = runemalloc(n);
+	runemove(f->name, name, n);
+	f->nname = n;
+	f->unread = TRUE;
+}
+
+void
+fileunsetname(File *f, Buffer *delta)
+{
+	Undo u;
+
+	/* undo a file name change by restoring old name */
+	u.type = Filename;
+	u.mod = f->mod;
+	u.seq = f->seq;
+	u.p0 = 0;	/* unused */
+	u.n = f->nname;
+	if(f->nname)
+		bufinsert(delta, delta->nc, f->name, f->nname);
+	bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+uint
+fileload(File *f, uint p0, int fd, int *nulls)
+{
+	if(f->seq > 0)
+		error("undo in file.load unimplemented");
+	return bufload(&f->b, p0, fd, nulls);
+}
+
+/* return sequence number of pending redo */
+uint
+fileredoseq(File *f)
+{
+	Undo u;
+	Buffer *delta;
+
+	delta = &f->epsilon;
+	if(delta->nc == 0)
+		return 0;
+	bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize);
+	return u.seq;
+}
+
+void
+fileundo(File *f, int isundo, uint *q0p, uint *q1p)
+{
+	Undo u;
+	Rune *buf;
+	uint i, j, n, up;
+	uint stop;
+	Buffer *delta, *epsilon;
+
+	if(isundo){
+		/* undo; reverse delta onto epsilon, seq decreases */
+		delta = &f->delta;
+		epsilon = &f->epsilon;
+		stop = f->seq;
+	}else{
+		/* redo; reverse epsilon onto delta, seq increases */
+		delta = &f->epsilon;
+		epsilon = &f->delta;
+		stop = 0;	/* don't know yet */
+	}
+
+	buf = fbufalloc();
+	while(delta->nc > 0){
+		up = delta->nc-Undosize;
+		bufread(delta, up, (Rune*)&u, Undosize);
+		if(isundo){
+			if(u.seq < stop){
+				f->seq = u.seq;
+				goto Return;
+			}
+		}else{
+			if(stop == 0)
+				stop = u.seq;
+			if(u.seq > stop)
+				goto Return;
+		}
+		switch(u.type){
+		default:
+			fprint(2, "undo: 0x%ux\n", u.type);
+			abort();
+			break;
+
+		case Delete:
+			f->seq = u.seq;
+			fileundelete(f, epsilon, u.p0, u.p0+u.n);
+			f->mod = u.mod;
+			bufdelete(&f->b, u.p0, u.p0+u.n);
+			for(j=0; j<f->ntext; j++)
+				textdelete(f->text[j], u.p0, u.p0+u.n, FALSE);
+			*q0p = u.p0;
+			*q1p = u.p0;
+			break;
+
+		case Insert:
+			f->seq = u.seq;
+			fileuninsert(f, epsilon, u.p0, u.n);
+			f->mod = u.mod;
+			up -= u.n;
+			for(i=0; i<u.n; i+=n){
+				n = u.n - i;
+				if(n > RBUFSIZE)
+					n = RBUFSIZE;
+				bufread(delta, up+i, buf, n);
+				bufinsert(&f->b, u.p0+i, buf, n);
+				for(j=0; j<f->ntext; j++)
+					textinsert(f->text[j], u.p0+i, buf, n, FALSE);
+			}
+			*q0p = u.p0;
+			*q1p = u.p0+u.n;
+			break;
+
+		case Filename:
+			f->seq = u.seq;
+			fileunsetname(f, epsilon);
+			f->mod = u.mod;
+			up -= u.n;
+			free(f->name);
+			if(u.n == 0)
+				f->name = nil;
+			else
+				f->name = runemalloc(u.n);
+			bufread(delta, up, f->name, u.n);
+			f->nname = u.n;
+			break;
+		}
+		bufdelete(delta, up, delta->nc);
+	}
+	if(isundo)
+		f->seq = 0;
+    Return:
+	fbuffree(buf);
+}
+
+void
+filereset(File *f)
+{
+	bufreset(&f->delta);
+	bufreset(&f->epsilon);
+	f->seq = 0;
+}
+
+void
+fileclose(File *f)
+{
+	free(f->name);
+	f->nname = 0;
+	f->name = nil;
+	free(f->text);
+	f->ntext = 0;
+	f->text = nil;
+	bufclose(&f->b);
+	bufclose(&f->delta);
+	bufclose(&f->epsilon);
+	elogclose(f);
+	free(f);
+}
+
+void
+filemark(File *f)
+{
+	if(f->epsilon.nc)
+		bufdelete(&f->epsilon, 0, f->epsilon.nc);
+	f->seq = seq;
+}
blob - /dev/null
blob + 69bbbb3b97ceeb3e127aaf2462eec7da5434dd4c (mode 644)
--- /dev/null
+++ src/cmd/acme/fns.h
@@ -0,0 +1,92 @@
+/*
+#pragma	varargck	argpos	warning	2
+#pragma	varargck	argpos	warningew	2
+*/
+
+void	warning(Mntdir*, char*, ...);
+void	warningew(Window*, Mntdir*, char*, ...);
+
+#define	fbufalloc()	emalloc(BUFSIZE)
+#define	fbuffree(x)	free(x)
+
+void	plumblook(Plumbmsg*m);
+void	plumbshow(Plumbmsg*m);
+void	acmeputsnarf(void);
+void	acmegetsnarf(void);
+int	tempfile(void);
+void	scrlresize(void);
+Font*	getfont(int, int, char*);
+char*	getarg(Text*, int, int, Rune**, int*);
+char*	getbytearg(Text*, int, int, char**);
+void	new(Text*, Text*, Text*, int, int, Rune*, int);
+void	undo(Text*, Text*, Text*, int, int, Rune*, int);
+void	scrsleep(uint);
+void	savemouse(Window*);
+void	restoremouse(Window*);
+void	clearmouse(void);
+void	allwindows(void(*)(Window*, void*), void*);
+uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
+
+Window*	errorwin(Mntdir*, int, Window*);
+Runestr cleanrname(Runestr);
+void	run(Window*, char*, Rune*, int, int, char*, char*, int);
+void fsysclose(void);
+void	setcurtext(Text*, int);
+int	isfilec(Rune);
+void	rxinit(void);
+int rxnull(void);
+Runestr	dirname(Text*, Rune*, int);
+void	error(char*);
+void	cvttorunes(char*, int, Rune*, int*, int*, int*);
+void*	tmalloc(uint);
+void	tfree(void);
+void	killprocs(void);
+void	killtasks(void);
+int	runeeq(Rune*, uint, Rune*, uint);
+int	ALEF_tid(void);
+void	iconinit(void);
+Timer*	timerstart(int);
+void	timerstop(Timer*);
+void	timercancel(Timer*);
+void	timerinit(void);
+void	cut(Text*, Text*, Text*, int, int, Rune*, int);
+void	paste(Text*, Text*, Text*, int, int, Rune*, int);
+void	get(Text*, Text*, Text*, int, int, Rune*, int);
+void	put(Text*, Text*, Text*, int, int, Rune*, int);
+void	putfile(File*, int, int, Rune*, int);
+void	fontx(Text*, Text*, Text*, int, int, Rune*, int);
+int	isalnum(Rune);
+void	execute(Text*, uint, uint, int, Text*);
+int	search(Text*, Rune*, uint);
+void	look3(Text*, uint, uint, int);
+void	editcmd(Text*, Rune*, uint);
+uint	min(uint, uint);
+uint	max(uint, uint);
+Window*	lookfile(Rune*, int);
+Window*	lookid(int, int);
+char*	runetobyte(Rune*, int);
+Rune*	bytetorune(char*, int*);
+void	fsysinit(void);
+Mntdir*	fsysmount(Rune*, int, Rune**, int);
+void		fsysdelid(Mntdir*);
+Xfid*		respond(Xfid*, Fcall*, char*);
+int		rxcompile(Rune*);
+int		rgetc(void*, uint);
+int		tgetc(void*, uint);
+int		isaddrc(int);
+int		isregexc(int);
+void *emalloc(uint);
+void *erealloc(void*, uint);
+char	*estrdup(char*);
+Range		address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint),  int*, uint*);
+int		rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int		rxbexecute(Text*, uint, Rangeset*);
+Window*	makenewwindow(Text *t);
+int	expand(Text*, uint, uint, Expand*);
+Rune*	skipbl(Rune*, int, int*);
+Rune*	findbl(Rune*, int, int*);
+char*	edittext(Window*, int, Rune*, int);	
+
+#define	runemalloc(a)		(Rune*)emalloc((a)*sizeof(Rune))
+#define	runerealloc(a, b)	(Rune*)erealloc((a), (b)*sizeof(Rune))
+#define	runemove(a, b, c)	memmove((a), (b), (c)*sizeof(Rune))
blob - /dev/null
blob + cd333dc7ac7d181ad1f981204456741d0e2f1703 (mode 644)
--- /dev/null
+++ src/cmd/acme/fsys.c
@@ -0,0 +1,717 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static	int	sfd;
+
+enum
+{
+	Nhash	= 16,
+	DEBUG	= 0
+};
+
+static	Fid	*fids[Nhash];
+
+Fid	*newfid(int);
+
+static	Xfid*	fsysflush(Xfid*, Fid*);
+static	Xfid*	fsysauth(Xfid*, Fid*);
+static	Xfid*	fsysversion(Xfid*, Fid*);
+static	Xfid*	fsysattach(Xfid*, Fid*);
+static	Xfid*	fsyswalk(Xfid*, Fid*);
+static	Xfid*	fsysopen(Xfid*, Fid*);
+static	Xfid*	fsyscreate(Xfid*, Fid*);
+static	Xfid*	fsysread(Xfid*, Fid*);
+static	Xfid*	fsyswrite(Xfid*, Fid*);
+static	Xfid*	fsysclunk(Xfid*, Fid*);
+static	Xfid*	fsysremove(Xfid*, Fid*);
+static	Xfid*	fsysstat(Xfid*, Fid*);
+static	Xfid*	fsyswstat(Xfid*, Fid*);
+
+Xfid* 	(*fcall[Tmax])(Xfid*, Fid*) =
+{
+	[Tflush]	= fsysflush,
+	[Tversion]	= fsysversion,
+	[Tauth]	= fsysauth,
+	[Tattach]	= fsysattach,
+	[Twalk]	= fsyswalk,
+	[Topen]	= fsysopen,
+	[Tcreate]	= fsyscreate,
+	[Tread]	= fsysread,
+	[Twrite]	= fsyswrite,
+	[Tclunk]	= fsysclunk,
+	[Tremove]= fsysremove,
+	[Tstat]	= fsysstat,
+	[Twstat]	= fsyswstat,
+};
+
+char Eperm[] = "permission denied";
+char Eexist[] = "file does not exist";
+char Enotdir[] = "not a directory";
+
+Dirtab dirtab[]=
+{
+	{ ".",			QTDIR,	Qdir,		0500|DMDIR },
+	{ "acme",		QTDIR,	Qacme,	0500|DMDIR },
+	{ "cons",		QTFILE,	Qcons,	0600 },
+	{ "consctl",	QTFILE,	Qconsctl,	0000 },
+	{ "draw",		QTDIR,	Qdraw,	0000|DMDIR },	/* to suppress graphics progs started in acme */
+	{ "editout",	QTFILE,	Qeditout,	0200 },
+	{ "index",		QTFILE,	Qindex,	0400 },
+	{ "label",		QTFILE,	Qlabel,	0600 },
+	{ "new",		QTDIR,	Qnew,	0500|DMDIR },
+	{ nil, }
+};
+
+Dirtab dirtabw[]=
+{
+	{ ".",			QTDIR,		Qdir,			0500|DMDIR },
+	{ "addr",		QTFILE,		QWaddr,		0600 },
+	{ "body",		QTAPPEND,	QWbody,		0600|DMAPPEND },
+	{ "ctl",		QTFILE,		QWctl,		0600 },
+	{ "data",		QTFILE,		QWdata,		0600 },
+	{ "editout",	QTFILE,		QWeditout,	0200 },
+	{ "event",		QTFILE,		QWevent,		0600 },
+	{ "rdsel",		QTFILE,		QWrdsel,		0400 },
+	{ "wrsel",		QTFILE,		QWwrsel,		0200 },
+	{ "tag",		QTAPPEND,	QWtag,		0600|DMAPPEND },
+	{ nil, }
+};
+
+typedef struct Mnt Mnt;
+struct Mnt
+{
+	QLock	lk;
+	int		id;
+	Mntdir	*md;
+};
+
+Mnt	mnt;
+
+Xfid*	respond(Xfid*, Fcall*, char*);
+int		dostat(int, Dirtab*, uchar*, int, uint);
+uint	getclock(void);
+
+char	*user = "Wile E. Coyote";
+static int closing = 0;
+int	messagesize = Maxblock+IOHDRSZ;	/* good start */
+
+void	fsysproc(void *);
+
+void
+fsysinit(void)
+{
+	int p[2];
+	int n, fd;
+	char buf[256], *u;
+
+	if(pipe(p) < 0)
+		error("can't create pipe");
+	if(post9pservice(p[0], "acme") < 0)
+		error("can't post service");
+	sfd = p[1];
+	fmtinstall('F', fcallfmt);
+	if((u = getuser()) != nil)
+		user = estrdup(u);
+	proccreate(fsysproc, nil, STACK);
+}
+
+void
+fsysproc(void *v)
+{
+	int n;
+	Xfid *x;
+	Fid *f;
+	Fcall t;
+	uchar *buf;
+
+	USED(v);
+	x = nil;
+	for(;;){
+		buf = emalloc(messagesize+UTFmax);	/* overflow for appending partial rune in xfidwrite */
+		n = read9pmsg(sfd, buf, messagesize);
+		if(n <= 0){
+			if(closing)
+				break;
+			error("i/o error on server channel");
+		}
+		if(x == nil){
+			sendp(cxfidalloc, nil);
+			x = recvp(cxfidalloc);
+		}
+		x->buf = buf;
+		if(convM2S(buf, n, &x->fcall) != n)
+			error("convert error in convM2S");
+		if(DEBUG)
+			fprint(2, "%F\n", &x->fcall);
+		if(fcall[x->fcall.type] == nil)
+			x = respond(x, &t, "bad fcall type");
+		else{
+			if(x->fcall.type==Tversion || x->fcall.type==Tauth)
+				f = nil;
+			else
+				f = newfid(x->fcall.fid);
+			x->f = f;
+			x  = (*fcall[x->fcall.type])(x, f);
+		}
+	}
+}
+
+Mntdir*
+fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl)
+{
+	Mntdir *m;
+	int id;
+
+	qlock(&mnt.lk);
+	id = ++mnt.id;
+	m = emalloc(sizeof *m);
+	m->id = id;
+	m->dir =  dir;
+	m->ref = 1;	/* one for Command, one will be incremented in attach */
+	m->ndir = ndir;
+	m->next = mnt.md;
+	m->incl = incl;
+	m->nincl = nincl;
+	mnt.md = m;
+	qunlock(&mnt.lk);
+	return m;
+}
+
+void
+fsysdelid(Mntdir *idm)
+{
+	Mntdir *m, *prev;
+	int i;
+	char buf[64];
+
+	if(idm == nil)
+		return;
+	qlock(&mnt.lk);
+	if(--idm->ref > 0){
+		qunlock(&mnt.lk);
+		return;
+	}
+	prev = nil;
+	for(m=mnt.md; m; m=m->next){
+		if(m == idm){
+			if(prev)
+				prev->next = m->next;
+			else
+				mnt.md = m->next;
+			for(i=0; i<m->nincl; i++)
+				free(m->incl[i]);
+			free(m->incl);
+			free(m->dir);
+			free(m);
+			qunlock(&mnt.lk);
+			return;
+		}
+		prev = m;
+	}
+	qunlock(&mnt.lk);
+	sprint(buf, "fsysdelid: can't find id %d\n", idm->id);
+	sendp(cerr, estrdup(buf));
+}
+
+/*
+ * Called only in exec.c:/^run(), from a different FD group
+ */
+Mntdir*
+fsysmount(Rune *dir, int ndir, Rune **incl, int nincl)
+{
+	return fsysaddid(dir, ndir, incl, nincl);
+}
+
+void
+fsysclose(void)
+{
+	closing = 1;
+	close(sfd);
+}
+
+Xfid*
+respond(Xfid *x, Fcall *t, char *err)
+{
+	int n;
+
+	if(err){
+		t->type = Rerror;
+		t->ename = err;
+	}else
+		t->type = x->fcall.type+1;
+	t->fid = x->fcall.fid;
+	t->tag = x->fcall.tag;
+	if(x->buf == nil)
+		x->buf = emalloc(messagesize);
+	n = convS2M(t, x->buf, messagesize);
+	if(n <= 0)
+		error("convert error in convS2M");
+	if(write(sfd, x->buf, n) != n)
+		error("write error in respond");
+	free(x->buf);
+	x->buf = nil;
+	if(DEBUG)
+		fprint(2, "r: %F\n", t);
+	return x;
+}
+
+static
+Xfid*
+fsysversion(Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	USED(f);
+	if(x->fcall.msize < 256)
+		return respond(x, &t, "version: message size too small");
+	messagesize = x->fcall.msize;
+	t.msize = messagesize;
+	if(strncmp(x->fcall.version, "9P2000", 6) != 0)
+		return respond(x, &t, "unrecognized 9P version");
+	t.version = "9P2000";
+	return respond(x, &t, nil);
+}
+
+static
+Xfid*
+fsysauth(Xfid *x, Fid *f)
+{
+	USED(f);
+	return respond(x, nil, "acme: authentication not required");
+}
+
+static
+Xfid*
+fsysflush(Xfid *x, Fid *f)
+{
+	USED(f);
+	sendp(x->c, xfidflush);
+	return nil;
+}
+
+static
+Xfid*
+fsysattach(Xfid *x, Fid *f)
+{
+	Fcall t;
+	int id;
+	Mntdir *m;
+
+	if(strcmp(x->fcall.uname, user) != 0)
+		return respond(x, &t, Eperm);
+	f->busy = TRUE;
+	f->open = FALSE;
+	f->qid.path = Qdir;
+	f->qid.type = QTDIR;
+	f->qid.vers = 0;
+	f->dir = dirtab;
+	f->nrpart = 0;
+	f->w = nil;
+	t.qid = f->qid;
+	f->mntdir = nil;
+	id = atoi(x->fcall.aname);
+	qlock(&mnt.lk);
+	for(m=mnt.md; m; m=m->next)
+		if(m->id == id){
+			f->mntdir = m;
+			m->ref++;
+			break;
+		}
+	if(m == nil)
+		sendp(cerr, estrdup("unknown id in attach"));
+	qunlock(&mnt.lk);
+	return respond(x, &t, nil);
+}
+
+static
+Xfid*
+fsyswalk(Xfid *x, Fid *f)
+{
+	Fcall t;
+	int c, i, j, id;
+	Qid q;
+	uchar type;
+	ulong path;
+	Fid *nf;
+	Dirtab *d, *dir;
+	Window *w;
+	char *err;
+
+	nf = nil;
+	w = nil;
+	if(f->open)
+		return respond(x, &t, "walk of open file");
+	if(x->fcall.fid != x->fcall.newfid){
+		nf = newfid(x->fcall.newfid);
+		if(nf->busy)
+			return respond(x, &t, "newfid already in use");
+		nf->busy = TRUE;
+		nf->open = FALSE;
+		nf->mntdir = f->mntdir;
+		if(f->mntdir)
+			f->mntdir->ref++;
+		nf->dir = f->dir;
+		nf->qid = f->qid;
+		nf->w = f->w;
+		nf->nrpart = 0;	/* not open, so must be zero */
+		if(nf->w)
+			incref(&nf->w->ref);
+		f = nf;	/* walk f */
+	}
+
+	t.nwqid = 0;
+	err = nil;
+	dir = nil;
+	id = WIN(f->qid);
+	q = f->qid;
+
+	if(x->fcall.nwname > 0){
+		for(i=0; i<x->fcall.nwname; i++){
+			if((q.type & QTDIR) == 0){
+				err = Enotdir;
+				break;
+			}
+
+			if(strcmp(x->fcall.wname[i], "..") == 0){
+				type = QTDIR;
+				path = Qdir;
+				id = 0;
+				if(w){
+					winclose(w);
+					w = nil;
+				}
+    Accept:
+				if(i == MAXWELEM){
+					err = "name too long";
+					break;
+				}
+				q.type = type;
+				q.vers = 0;
+				q.path = QID(id, path);
+				t.wqid[t.nwqid++] = q;
+				continue;
+			}
+
+			/* is it a numeric name? */
+			for(j=0; (c=x->fcall.wname[i][j]); j++)
+				if(c<'0' || '9'<c)
+					goto Regular;
+			/* yes: it's a directory */
+			if(w)	/* name has form 27/23; get out before losing w */
+				break;
+			id = atoi(x->fcall.wname[i]);
+			qlock(&row.lk);
+			w = lookid(id, FALSE);
+			if(w == nil){
+				qunlock(&row.lk);
+				break;
+			}
+			incref(&w->ref);	/* we'll drop reference at end if there's an error */
+			path = Qdir;
+			type = QTDIR;
+			qunlock(&row.lk);
+			dir = dirtabw;
+			goto Accept;
+	
+    Regular:
+//			if(FILE(f->qid) == Qacme)	/* empty directory */
+//				break;
+			if(strcmp(x->fcall.wname[i], "new") == 0){
+				if(w)
+					error("w set in walk to new");
+				sendp(cnewwindow, nil);	/* signal newwindowthread */
+				w = recvp(cnewwindow);	/* receive new window */
+				incref(&w->ref);
+				type = QTDIR;
+				path = QID(w->id, Qdir);
+				id = w->id;
+				dir = dirtabw;
+				goto Accept;
+			}
+
+			if(id == 0)
+				d = dirtab;
+			else
+				d = dirtabw;
+			d++;	/* skip '.' */
+			for(; d->name; d++)
+				if(strcmp(x->fcall.wname[i], d->name) == 0){
+					path = d->qid;
+					type = d->type;
+					dir = d;
+					goto Accept;
+				}
+
+			break;	/* file not found */
+		}
+
+		if(i==0 && err == nil)
+			err = Eexist;
+	}
+
+	if(err!=nil || t.nwqid<x->fcall.nwname){
+		if(nf){
+			nf->busy = FALSE;
+			fsysdelid(nf->mntdir);
+		}
+	}else if(t.nwqid  == x->fcall.nwname){
+		if(w){
+			f->w = w;
+			w = nil;	/* don't drop the reference */
+		}
+		if(dir)
+			f->dir = dir;
+		f->qid = q;
+	}
+
+	if(w != nil)
+		winclose(w);
+
+	return respond(x, &t, err);
+}
+
+static
+Xfid*
+fsysopen(Xfid *x, Fid *f)
+{
+	Fcall t;
+	int m;
+
+	/* can't truncate anything, so just disregard */
+	x->fcall.mode &= ~(OTRUNC|OCEXEC);
+	/* can't execute or remove anything */
+	if(x->fcall.mode==OEXEC || (x->fcall.mode&ORCLOSE))
+		goto Deny;
+	switch(x->fcall.mode){
+	default:
+		goto Deny;
+	case OREAD:
+		m = 0400;
+		break;
+	case OWRITE:
+		m = 0200;
+		break;
+	case ORDWR:
+		m = 0600;
+		break;
+	}
+	if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
+		goto Deny;
+
+	sendp(x->c, xfidopen);
+	return nil;
+
+    Deny:
+	return respond(x, &t, Eperm);
+}
+
+static
+Xfid*
+fsyscreate(Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	USED(f);
+	return respond(x, &t, Eperm);
+}
+
+static
+int
+idcmp(const void *a, const void *b)
+{
+	return *(int*)a - *(int*)b;
+}
+
+static
+Xfid*
+fsysread(Xfid *x, Fid *f)
+{
+	Fcall t;
+	uchar *b;
+	int i, id, n, o, e, j, k, *ids, nids;
+	Dirtab *d, dt;
+	Column *c;
+	uint clock, len;
+	char buf[16];
+
+	if(f->qid.type & QTDIR){
+		if(FILE(f->qid) == Qacme){	/* empty dir */
+			t.data = nil;
+			t.count = 0;
+			respond(x, &t, nil);
+			return x;
+		}
+		o = x->fcall.offset;
+		e = x->fcall.offset+x->fcall.count;
+		clock = getclock();
+		b = emalloc(messagesize);
+		id = WIN(f->qid);
+		n = 0;
+		if(id > 0)
+			d = dirtabw;
+		else
+			d = dirtab;
+		d++;	/* first entry is '.' */
+		for(i=0; d->name!=nil && i<e; i+=len){
+			len = dostat(WIN(x->f->qid), d, b+n, x->fcall.count-n, clock);
+			if(len <= BIT16SZ)
+				break;
+			if(i >= o)
+				n += len;
+			d++;
+		}
+		if(id == 0){
+			qlock(&row.lk);
+			nids = 0;
+			ids = nil;
+			for(j=0; j<row.ncol; j++){
+				c = row.col[j];
+				for(k=0; k<c->nw; k++){
+					ids = realloc(ids, (nids+1)*sizeof(int));
+					ids[nids++] = c->w[k]->id;
+				}
+			}
+			qunlock(&row.lk);
+			qsort(ids, nids, sizeof ids[0], idcmp);
+			j = 0;
+			dt.name = buf;
+			for(; j<nids && i<e; i+=len){
+				k = ids[j];
+				sprint(dt.name, "%d", k);
+				dt.qid = QID(k, Qdir);
+				dt.type = QTDIR;
+				dt.perm = DMDIR|0700;
+				len = dostat(k, &dt, b+n, x->fcall.count-n, clock);
+				if(len == 0)
+					break;
+				if(i >= o)
+					n += len;
+				j++;
+			}
+			free(ids);
+		}
+		t.data = (char*)b;
+		t.count = n;
+		respond(x, &t, nil);
+		free(b);
+		return x;
+	}
+	sendp(x->c, xfidread);
+	return nil;
+}
+
+static
+Xfid*
+fsyswrite(Xfid *x, Fid *f)
+{
+	USED(f);
+	sendp(x->c, xfidwrite);
+	return nil;
+}
+
+static
+Xfid*
+fsysclunk(Xfid *x, Fid *f)
+{
+	fsysdelid(f->mntdir);
+	sendp(x->c, xfidclose);
+	return nil;
+}
+
+static
+Xfid*
+fsysremove(Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	USED(f);
+	return respond(x, &t, Eperm);
+}
+
+static
+Xfid*
+fsysstat(Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	t.stat = emalloc(messagesize-IOHDRSZ);
+	t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock());
+	x = respond(x, &t, nil);
+	free(t.stat);
+	return x;
+}
+
+static
+Xfid*
+fsyswstat(Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	USED(f);
+	return respond(x, &t, Eperm);
+}
+
+Fid*
+newfid(int fid)
+{
+	Fid *f, *ff, **fh;
+
+	ff = nil;
+	fh = &fids[fid&(Nhash-1)];
+	for(f=*fh; f; f=f->next)
+		if(f->fid == fid)
+			return f;
+		else if(ff==nil && f->busy==FALSE)
+			ff = f;
+	if(ff){
+		ff->fid = fid;
+		return ff;
+	}
+	f = emalloc(sizeof *f);
+	f->fid = fid;
+	f->next = *fh;
+	*fh = f;
+	return f;
+}
+
+uint
+getclock(void)
+{
+/*
+	char buf[32];
+
+	buf[0] = '\0';
+	pread(clockfd, buf, sizeof buf, 0);
+	return atoi(buf);
+*/	
+	return time(0);
+}
+
+int
+dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock)
+{
+	Dir d;
+
+	d.qid.path = QID(id, dir->qid);
+	d.qid.vers = 0;
+	d.qid.type = dir->type;
+	d.mode = dir->perm;
+	d.length = 0;	/* would be nice to do better */
+	d.name = dir->name;
+	d.uid = user;
+	d.gid = user;
+	d.muid = user;
+	d.atime = clock;
+	d.mtime = clock;
+	return convD2M(&d, buf, nbuf);
+}
blob - /dev/null
blob + 9f54f9e4626bb73ad3f596787758b8cd6930b167 (mode 644)
--- /dev/null
+++ src/cmd/acme/look.c
@@ -0,0 +1,772 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <regexp.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+Window*	openfile(Text*, Expand*);
+
+int	nuntitled;
+
+void
+look3(Text *t, uint q0, uint q1, int external)
+{
+	int n, c, f, expanded;
+	Text *ct;
+	Expand e;
+	Rune *r;
+	uint p;
+	Plumbmsg *m;
+	Runestr dir;
+	char buf[32];
+
+	ct = seltext;
+	if(ct == nil)
+		seltext = t;
+	expanded = expand(t, q0, q1, &e);
+	if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
+		/* send alphanumeric expansion to external client */
+		if(expanded == FALSE)
+			return;
+		f = 0;
+		if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
+			f = 1;		/* acme can do it without loading a file */
+		if(q0!=e.q0 || q1!=e.q1)
+			f |= 2;	/* second (post-expand) message follows */
+		if(e.nname)
+			f |= 4;	/* it's a file name */
+		c = 'l';
+		if(t->what == Body)
+			c = 'L';
+		n = q1-q0;
+		if(n <= EVENTSIZE){
+			r = runemalloc(n);
+			bufread(&t->file->b, q0, r, n);
+			winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
+			free(r);
+		}else
+			winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
+		if(q0==e.q0 && q1==e.q1)
+			return;
+		if(e.nname){
+			n = e.nname;
+			if(e.a1 > e.a0)
+				n += 1+(e.a1-e.a0);
+			r = runemalloc(n);
+			runemove(r, e.name, e.nname);
+			if(e.a1 > e.a0){
+				r[e.nname] = ':';
+				bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0);
+			}
+		}else{
+			n = e.q1 - e.q0;
+			r = runemalloc(n);
+			bufread(&t->file->b, e.q0, r, n);
+		}
+		f &= ~2;
+		if(n <= EVENTSIZE)
+			winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
+		else
+			winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
+		free(r);
+		goto Return;
+	}
+	if(plumbsendfd >= 0){
+		/* send whitespace-delimited word to plumber */
+		m = emalloc(sizeof(Plumbmsg));
+		m->src = estrdup("acme");
+		m->dst = nil;
+		dir = dirname(t, nil, 0);
+		if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
+			free(dir.r);
+			dir.r = nil;
+			dir.nr = 0;
+		}
+		if(dir.nr == 0)
+			m->wdir = estrdup(wdir);
+		else
+			m->wdir = runetobyte(dir.r, dir.nr);
+		free(dir.r);
+		m->type = estrdup("text");
+		m->attr = nil;
+		buf[0] = '\0';
+		if(q1 == q0){
+			if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+				q0 = t->q0;
+				q1 = t->q1;
+			}else{
+				p = q0;
+				while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
+					q0--;
+				while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
+					q1++;
+				if(q1 == q0){
+					plumbfree(m);
+					goto Return;
+				}
+				sprint(buf, "click=%d", p-q0);
+				m->attr = plumbunpackattr(buf);
+			}
+		}
+		r = runemalloc(q1-q0);
+		bufread(&t->file->b, q0, r, q1-q0);
+		m->data = runetobyte(r, q1-q0);
+		m->ndata = strlen(m->data);
+		free(r);
+		if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){
+			plumbfree(m);
+			goto Return;
+		}
+		plumbfree(m);
+		/* plumber failed to match; fall through */
+	}
+
+	/* interpret alphanumeric string ourselves */
+	if(expanded == FALSE)
+		return;
+	if(e.name || e.u.at)
+		openfile(t, &e);
+	else{
+		if(t->w == nil)
+			return;
+		ct = &t->w->body;
+		if(t->w != ct->w)
+			winlock(ct->w, 'M');
+		if(t == ct)
+			textsetselect(ct, e.q1, e.q1);
+		n = e.q1 - e.q0;
+		r = runemalloc(n);
+		bufread(&t->file->b, e.q0, r, n);
+		if(search(ct, r, n) && e.jump)
+			moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4)));
+		if(t->w != ct->w)
+			winunlock(ct->w);
+		free(r);
+	}
+
+   Return:
+	free(e.name);
+	free(e.bname);
+}
+
+int
+plumbgetc(void *a, uint n)
+{
+	Rune *r;
+
+	r = a;
+	if(n<0 || n>runestrlen(r))
+		return 0;
+	return r[n];
+}
+
+void
+plumblook(Plumbmsg *m)
+{
+	Expand e;
+	char *addr;
+
+	if(m->ndata >= BUFSIZE){
+		warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
+		return;
+	}
+	e.q0 = 0;
+	e.q1 = 0;
+	if(m->data[0] == '\0')
+		return;
+	e.u.ar = nil;
+	e.bname = m->data;
+	e.name = bytetorune(e.bname, &e.nname);
+	e.jump = TRUE;
+	e.a0 = 0;
+	e.a1 = 0;
+	addr = plumblookup(m->attr, "addr");
+	if(addr != nil){
+		e.u.ar = bytetorune(addr, &e.a1);
+		e.agetc = plumbgetc;
+	}
+	openfile(nil, &e);
+	free(e.name);
+	free(e.u.at);
+}
+
+void
+plumbshow(Plumbmsg *m)
+{
+	Window *w;
+	Rune rb[256], *r;
+	int nb, nr;
+	Runestr rs;
+	char *name, *p, namebuf[16];
+
+	w = makenewwindow(nil);
+	name = plumblookup(m->attr, "filename");
+	if(name == nil){
+		name = namebuf;
+		nuntitled++;
+		snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
+	}
+	p = nil;
+	if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
+		nb = strlen(m->wdir) + 1 + strlen(name) + 1;
+		p = emalloc(nb);
+		snprint(p, nb, "%s/%s", m->wdir, name);
+		name = p;
+	}
+	cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
+	free(p);
+	rs = cleanrname((Runestr){rb, nr});
+	winsetname(w, rs.r, rs.nr);
+	r = runemalloc(m->ndata);
+	cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
+	textinsert(&w->body, 0, r, nr, TRUE);
+	free(r);
+	w->body.file->mod = FALSE;
+	w->dirty = FALSE;
+	winsettag(w);
+	textscrdraw(&w->body);
+	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
+}
+
+int
+search(Text *ct, Rune *r, uint n)
+{
+	uint q, nb, maxn;
+	int around;
+	Rune *s, *b, *c;
+
+	if(n==0 || n>ct->file->b.nc)
+		return FALSE;
+	if(2*n > RBUFSIZE){
+		warning(nil, "string too long\n");
+		return FALSE;
+	}
+	maxn = max(2*n, RBUFSIZE);
+	s = fbufalloc();
+	b = s;
+	nb = 0;
+	b[nb] = 0;
+	around = 0;
+	q = ct->q1;
+	for(;;){
+		if(q >= ct->file->b.nc){
+			q = 0;
+			around = 1;
+			nb = 0;
+			b[nb] = 0;
+		}
+		if(nb > 0){
+			c = runestrchr(b, r[0]);
+			if(c == nil){
+				q += nb;
+				nb = 0;
+				b[nb] = 0;
+				if(around && q>=ct->q1)
+					break;
+				continue;
+			}
+			q += (c-b);
+			nb -= (c-b);
+			b = c;
+		}
+		/* reload if buffer covers neither string nor rest of file */
+		if(nb<n && nb!=ct->file->b.nc-q){
+			nb = ct->file->b.nc-q;
+			if(nb >= maxn)
+				nb = maxn-1;
+			bufread(&ct->file->b, q, s, nb);
+			b = s;
+			b[nb] = '\0';
+		}
+		/* this runeeq is fishy but the null at b[nb] makes it safe */
+		if(runeeq(b, n, r, n)==TRUE){
+			if(ct->w){
+				textshow(ct, q, q+n, 1);
+				winsettag(ct->w);
+			}else{
+				ct->q0 = q;
+				ct->q1 = q+n;
+			}
+			seltext = ct;
+			fbuffree(s);
+			return TRUE;
+		}
+		if(around && q>=ct->q1)
+			break;
+		--nb;
+		b++;
+		q++;
+	}
+	fbuffree(s);
+	return FALSE;
+}
+
+int
+isfilec(Rune r)
+{
+	static Rune Lx[] = { '.', '-', '+', '/', ':', 0 };
+	if(isalnum(r))
+		return TRUE;
+	if(runestrchr(Lx, r))
+		return TRUE;
+	return FALSE;
+}
+
+Runestr
+cleanrname(Runestr rs)
+{
+	int i, j, found;
+	Rune *b;
+	int n;
+	static Rune Lslashdotdot[] = { '/', '.', '.', 0 };
+
+	b = rs.r;
+	n = rs.nr;
+
+	/* compress multiple slashes */
+	for(i=0; i<n-1; i++)
+		if(b[i]=='/' && b[i+1]=='/'){
+			runemove(b+i, b+i+1, n-i-1);
+			--n;
+			--i;
+		}
+	/*  eliminate ./ */
+	for(i=0; i<n-1; i++)
+		if(b[i]=='.' && b[i+1]=='/' && (i==0 || b[i-1]=='/')){
+			runemove(b+i, b+i+2, n-i-2);
+			n -= 2;
+			--i;
+		}
+	/* eliminate trailing . */
+	if(n>=2 && b[n-2]=='/' && b[n-1]=='.')
+		--n;
+	do{
+		/* compress xx/.. */
+		found = FALSE;
+		for(i=1; i<=n-3; i++)
+			if(runeeq(b+i, 3, Lslashdotdot, 3)){
+				if(i==n-3 || b[i+3]=='/'){
+					found = TRUE;
+					break;
+				}
+			}
+		if(found)
+			for(j=i-1; j>=0; --j)
+				if(j==0 || b[j-1]=='/'){
+					i += 3;		/* character beyond .. */
+					if(i<n && b[i]=='/')
+						++i;
+					runemove(b+j, b+i, n-i);
+					n -= (i-j);
+					break;
+				}
+	}while(found);
+	if(n == 0){
+		*b = '.';
+		n = 1;
+	}
+	return (Runestr){b, n};
+}
+
+Runestr
+includefile(Rune *dir, Rune *file, int nfile)
+{
+	int m, n;
+	char *a;
+	Rune *r;
+	static Rune Lslash[] = { '/', 0 };
+
+	m = runestrlen(dir);
+	a = emalloc((m+1+nfile)*UTFmax+1);
+	sprint(a, "%S/%.*S", dir, nfile, file);
+	n = access(a, 0);
+	free(a);
+	if(n < 0)
+		return (Runestr){nil, 0};
+	r = runemalloc(m+1+nfile);
+	runemove(r, dir, m);
+	runemove(r+m, Lslash, 1);
+	runemove(r+m+1, file, nfile);
+	free(file);
+	return cleanrname((Runestr){r, m+1+nfile});
+}
+
+static	Rune	*objdir;
+
+Runestr
+includename(Text *t, Rune *r, int n)
+{
+	Window *w;
+	char buf[128];
+	Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+	Runestr file;
+	int i;
+
+	if(objdir==nil && objtype!=nil){
+		sprint(buf, "/%s/include", objtype);
+		objdir = bytetorune(buf, &i);
+		objdir = runerealloc(objdir, i+1);
+		objdir[i] = '\0';	
+	}
+
+	w = t->w;
+	if(n==0 || r[0]=='/' || w==nil)
+		goto Rescue;
+	if(n>2 && r[0]=='.' && r[1]=='/')
+		goto Rescue;
+	file.r = nil;
+	file.nr = 0;
+	for(i=0; i<w->nincl && file.r==nil; i++)
+		file = includefile(w->incl[i], r, n);
+
+	if(file.r == nil)
+		file = includefile(Lsysinclude, r, n);
+	if(file.r==nil && objdir!=nil)
+		file = includefile(objdir, r, n);
+	if(file.r == nil)
+		goto Rescue;
+	return file;
+
+    Rescue:
+	return (Runestr){r, n};
+}
+
+Runestr
+dirname(Text *t, Rune *r, int n)
+{
+	Rune *b, c;
+	uint m, nt;
+	int slash;
+	Runestr tmp;
+
+	b = nil;
+	if(t==nil || t->w==nil)
+		goto Rescue;
+	nt = t->w->tag.file->b.nc;
+	if(nt == 0)
+		goto Rescue;
+	if(n>=1 &&  r[0]=='/')
+		goto Rescue;
+	b = runemalloc(nt+n+1);
+	bufread(&t->w->tag.file->b, 0, b, nt);
+	slash = -1;
+	for(m=0; m<nt; m++){
+		c = b[m];
+		if(c == '/')
+			slash = m;
+		if(c==' ' || c=='\t')
+			break;
+	}
+	if(slash < 0)
+		goto Rescue;
+	runemove(b+slash+1, r, n);
+	free(r);
+	return cleanrname((Runestr){b, slash+1+n});
+
+    Rescue:
+	free(b);
+	tmp = (Runestr){r, n};
+	if(r)
+		return cleanrname(tmp);
+	return tmp;
+}
+
+int
+expandfile(Text *t, uint q0, uint q1, Expand *e)
+{
+	int i, n, nname, colon, eval;
+	uint amin, amax;
+	Rune *r, c;
+	Window *w;
+	Runestr rs;
+
+	amax = q1;
+	if(q1 == q0){
+		colon = -1;
+		while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){
+			if(c == ':'){
+				colon = q1;
+				break;
+			}
+			q1++;
+		}
+		while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
+			q0--;
+			if(colon<0 && c==':')
+				colon = q0;
+		}
+		/*
+		 * if it looks like it might begin file: , consume address chars after :
+		 * otherwise terminate expansion at :
+		 */
+		if(colon >= 0){
+			q1 = colon;
+			if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){
+				q1 = colon+1;
+				while(q1<t->file->b.nc-1 && isaddrc(textreadc(t, q1)))
+					q1++;
+			}
+		}
+		if(q1 > q0)
+			if(colon >= 0){	/* stop at white space */
+				for(amax=colon+1; amax<t->file->b.nc; amax++)
+					if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
+						break;
+			}else
+				amax = t->file->b.nc;
+	}
+	amin = amax;
+	e->q0 = q0;
+	e->q1 = q1;
+	n = q1-q0;
+	if(n == 0)
+		return FALSE;
+	/* see if it's a file name */
+	r = runemalloc(n);
+	bufread(&t->file->b, q0, r, n);
+	/* first, does it have bad chars? */
+	nname = -1;
+	for(i=0; i<n; i++){
+		c = r[i];
+		if(c==':' && nname<0){
+			if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
+				amin = q0+i;
+			else
+				goto Isntfile;
+			nname = i;
+		}
+	}
+	if(nname == -1)
+		nname = n;
+	for(i=0; i<nname; i++)
+		if(!isfilec(r[i]))
+			goto Isntfile;
+	/*
+	 * See if it's a file name in <>, and turn that into an include
+	 * file name if so.  Should probably do it for "" too, but that's not
+	 * restrictive enough syntax and checking for a #include earlier on the
+	 * line would be silly.
+	 */
+	if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){
+		rs = includename(t, r, nname);
+		r = rs.r;
+		nname = rs.nr;
+	}
+	else if(amin == q0)
+		goto Isfile;
+	else{
+		rs = dirname(t, r, nname);
+		r = rs.r;
+		nname = rs.nr;
+	}
+	e->bname = runetobyte(r, nname);
+	/* if it's already a window name, it's a file */
+	w = lookfile(r, nname);
+	if(w != nil)
+		goto Isfile;
+	/* if it's the name of a file, it's a file */
+	if(access(e->bname, 0) < 0){
+		free(e->bname);
+		e->bname = nil;
+		goto Isntfile;
+	}
+
+  Isfile:
+	e->name = r;
+	e->nname = nname;
+	e->u.at = t;
+	e->a0 = amin+1;
+	eval = FALSE;
+	address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
+	return TRUE;
+
+   Isntfile:
+	free(r);
+	return FALSE;
+}
+
+int
+expand(Text *t, uint q0, uint q1, Expand *e)
+{
+	memset(e, 0, sizeof *e);
+	e->agetc = tgetc;
+	/* if in selection, choose selection */
+	e->jump = TRUE;
+	if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+		q0 = t->q0;
+		q1 = t->q1;
+		if(t->what == Tag)
+			e->jump = FALSE;
+	}
+
+	if(expandfile(t, q0, q1, e))
+		return TRUE;
+
+	if(q0 == q1){
+		while(q1<t->file->b.nc && isalnum(textreadc(t, q1)))
+			q1++;
+		while(q0>0 && isalnum(textreadc(t, q0-1)))
+			q0--;
+	}
+	e->q0 = q0;
+	e->q1 = q1;
+	return q1 > q0;
+}
+
+Window*
+lookfile(Rune *s, int n)
+{
+	int i, j, k;
+	Window *w;
+	Column *c;
+	Text *t;
+
+	/* avoid terminal slash on directories */
+	if(n>1 && s[n-1] == '/')
+		--n;
+	for(j=0; j<row.ncol; j++){
+		c = row.col[j];
+		for(i=0; i<c->nw; i++){
+			w = c->w[i];
+			t = &w->body;
+			k = t->file->nname;
+			if(k>1 && t->file->name[k-1] == '/')
+				k--;
+			if(runeeq(t->file->name, k, s, n)){
+				w = w->body.file->curtext->w;
+				if(w->col != nil)	/* protect against race deleting w */
+					return w;
+			}
+		}
+	}
+	return nil;
+}
+
+Window*
+lookid(int id, int dump)
+{
+	int i, j;
+	Window *w;
+	Column *c;
+
+	for(j=0; j<row.ncol; j++){
+		c = row.col[j];
+		for(i=0; i<c->nw; i++){
+			w = c->w[i];
+			if(dump && w->dumpid == id)
+				return w;
+			if(!dump && w->id == id)
+				return w;
+		}
+	}
+	return nil;
+}
+
+
+Window*
+openfile(Text *t, Expand *e)
+{
+	Range r;
+	Window *w, *ow;
+	int eval, i, n;
+	Rune *rp;
+	uint dummy;
+
+	if(e->nname == 0){
+		w = t->w;
+		if(w == nil)
+			return nil;
+	}else
+		w = lookfile(e->name, e->nname);
+	if(w){
+		t = &w->body;
+		if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */
+			colgrow(t->col, t->col->w[0], 1);
+	}else{
+		ow = nil;
+		if(t)
+			ow = t->w;
+		w = makenewwindow(t);
+		t = &w->body;
+		winsetname(w, e->name, e->nname);
+		textload(t, 0, e->bname, 1);
+		t->file->mod = FALSE;
+		t->w->dirty = FALSE;
+		winsettag(t->w);
+		textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc);
+		if(ow != nil)
+			for(i=ow->nincl; --i>=0; ){
+				n = runestrlen(ow->incl[i]);
+				rp = runemalloc(n);
+				runemove(rp, ow->incl[i], n);
+				winaddincl(w, rp, n);
+			}
+	}
+	if(e->a1 == e->a0)
+		eval = FALSE;
+	else{
+		eval = TRUE;
+		r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy);
+		if(eval == FALSE)
+			e->jump = FALSE;	/* don't jump if invalid address */
+	}
+	if(eval == FALSE){
+		r.q0 = t->q0;
+		r.q1 = t->q1;
+	}
+	textshow(t, r.q0, r.q1, 1);
+	winsettag(t->w);
+	seltext = t;
+	if(e->jump)
+		moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4)));
+	return w;
+}
+
+void
+new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg)
+{
+	int ndone;
+	Rune *a, *f;
+	int na, nf;
+	Expand e;
+	Runestr rs;
+
+	getarg(argt, FALSE, TRUE, &a, &na);
+	if(a){
+		new(et, t, nil, flag1, flag2, a, na);
+		if(narg == 0)
+			return;
+	}
+	/* loop condition: *arg is not a blank */
+	for(ndone=0; ; ndone++){
+		a = findbl(arg, narg, &na);
+		if(a == arg){
+			if(ndone==0 && et->col!=nil)
+				winsettag(coladd(et->col, nil, nil, -1));
+			break;
+		}
+		nf = narg-na;
+		f = runemalloc(nf);
+		runemove(f, arg, nf);
+		rs = dirname(et, f, nf);
+		f = rs.r;
+		nf = rs.nr;
+		memset(&e, 0, sizeof e);
+		e.name = f;
+		e.nname = nf;
+		e.bname = runetobyte(f, nf);
+		e.jump = TRUE;
+		openfile(et, &e);
+		free(f);
+		free(e.bname);
+		arg = skipbl(a, na, &narg);
+	}
+}
blob - /dev/null
blob + 84149eb97a6e620216a58418a652e3980618db8c (mode 644)
--- /dev/null
+++ src/cmd/acme/mkfile
@@ -0,0 +1,41 @@
+PLAN9=../../..
+<$PLAN9/src/mkhdr
+
+TARG=acme
+
+OFILES=\
+	acme.$O\
+	addr.$O\
+	buff.$O\
+	cols.$O\
+	disk.$O\
+	ecmd.$O\
+	edit.$O\
+	elog.$O\
+	exec.$O\
+	file.$O\
+	fsys.$O\
+	look.$O\
+	regx.$O\
+	rows.$O\
+	scrl.$O\
+	text.$O\
+	time.$O\
+	util.$O\
+	wind.$O\
+	xfid.$O\
+
+HFILES=dat.h\
+	edit.h\
+	fns.h\
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+
+LDFLAGS=$LDFLAGS -lfs -lmux -lplumb -lthread -lframe -ldraw -lbio -l9 -lfmt -lutf -L$X11/lib -lX11
+
+edit.$O ecmd.$O elog.$O:	edit.h
blob - /dev/null
blob + f934187b950516c89e062f1acb460a7062285eda (mode 644)
--- /dev/null
+++ src/cmd/acme/regx.c
@@ -0,0 +1,835 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+Rangeset	sel;
+Rune		*lastregexp;
+
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+struct Inst
+{
+	uint	type;	/* < 0x10000 ==> literal, otherwise action */
+	union {
+		int sid;
+		int subid;
+		int class;
+		Inst *other;
+		Inst *right;
+	} u;
+	union{
+		Inst *left;
+		Inst *next;
+	} u1;
+};
+
+#define	NPROG	1024
+Inst	program[NPROG];
+Inst	*progp;
+Inst	*startinst;	/* First inst. of program; might not be program[0] */
+Inst	*bstartinst;	/* same for backwards machine */
+Channel	*rechan;	/* chan(Inst*) */
+
+typedef struct Ilist Ilist;
+struct Ilist
+{
+	Inst	*inst;		/* Instruction of the thread */
+	Rangeset se;
+	uint	startp;		/* first char of match */
+};
+
+#define	NLIST	128
+
+Ilist	*tl, *nl;	/* This list, next list */
+Ilist	list[2][NLIST];
+static	Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ *	0x100xx are operators, value == precedence
+ *	0x200xx are tokens, i.e. operands for operators
+ */
+#define	OPERATOR	0x10000	/* Bitmask of all operators */
+#define	START		0x10000	/* Start, used for marker on stack */
+#define	RBRA		0x10001	/* Right bracket, ) */
+#define	LBRA		0x10002	/* Left bracket, ( */
+#define	OR		0x10003	/* Alternation, | */
+#define	CAT		0x10004	/* Concatentation, implicit operator */
+#define	STAR		0x10005	/* Closure, * */
+#define	PLUS		0x10006	/* a+ == aa* */
+#define	QUEST		0x10007	/* a? == a|nothing, i.e. 0 or 1 a's */
+#define	ANY		0x20000	/* Any character but newline, . */
+#define	NOP		0x20001	/* No operation, internal use only */
+#define	BOL		0x20002	/* Beginning of line, ^ */
+#define	EOL		0x20003	/* End of line, $ */
+#define	CCLASS		0x20004	/* Character class, [] */
+#define	NCCLASS		0x20005	/* Negated character class, [^] */
+#define	END		0x20077	/* Terminate: match found */
+
+#define	ISATOR		0x10000
+#define	ISAND		0x20000
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+	Inst	*first;
+	Inst	*last;
+};
+
+#define	NSTACK	20
+Node	andstack[NSTACK];
+Node	*andp;
+int	atorstack[NSTACK];
+int	*atorp;
+int	lastwasand;	/* Last token was operand */
+int	cursubid;
+int	subidstack[NSTACK];
+int	*subidp;
+int	backwards;
+int	nbra;
+Rune	*exprp;		/* pointer to next character in source expression */
+#define	DCLASS	10	/* allocation increment */
+int	nclass;		/* number active */
+int	Nclass;		/* high water mark */
+Rune	**class;
+int	negateclass;
+
+void	addinst(Ilist *l, Inst *inst, Rangeset *sep);
+void	newmatch(Rangeset*);
+void	bnewmatch(Rangeset*);
+void	pushand(Inst*, Inst*);
+void	pushator(int);
+Node	*popand(int);
+int	popator(void);
+void	startlex(Rune*);
+int	lex(void);
+void	operator(int);
+void	operand(int);
+void	evaluntil(int);
+void	optimize(Inst*);
+void	bldcclass(void);
+
+void
+rxinit(void)
+{
+	rechan = chancreate(sizeof(Inst*), 0);
+	lastregexp = runemalloc(1);
+}
+
+void
+regerror(char *e)
+{
+	lastregexp[0] = 0;
+	warning(nil, "regexp: %s\n", e);
+	sendp(rechan, nil);
+	threadexits(nil);
+}
+
+Inst *
+newinst(int t)
+{
+	if(progp >= &program[NPROG])
+		regerror("expression too long");
+	progp->type = t;
+	progp->u1.left = nil;
+	progp->u.right = nil;
+	return progp++;
+}
+
+void
+realcompile(void *arg)
+{
+	int token;
+	Rune *s;
+
+	threadsetname("regcomp");
+	s = arg;
+	startlex(s);
+	atorp = atorstack;
+	andp = andstack;
+	subidp = subidstack;
+	cursubid = 0;
+	lastwasand = FALSE;
+	/* Start with a low priority operator to prime parser */
+	pushator(START-1);
+	while((token=lex()) != END){
+		if((token&ISATOR) == OPERATOR)
+			operator(token);
+		else
+			operand(token);
+	}
+	/* Close with a low priority operator */
+	evaluntil(START);
+	/* Force END */
+	operand(END);
+	evaluntil(START);
+	if(nbra)
+		regerror("unmatched `('");
+	--andp;	/* points to first and only operand */
+	sendp(rechan, andp->first);
+	threadexits(nil);
+}
+
+/* r is null terminated */
+int
+rxcompile(Rune *r)
+{
+	int i, nr;
+	Inst *oprogp;
+
+	nr = runestrlen(r)+1;
+	if(runeeq(lastregexp, runestrlen(lastregexp)+1, r, nr)==TRUE)
+		return TRUE;
+	lastregexp[0] = 0;
+	for(i=0; i<nclass; i++)
+		free(class[i]);
+	nclass = 0;
+	progp = program;
+	backwards = FALSE;
+	bstartinst = nil;
+	threadcreate(realcompile, r, STACK);
+	startinst = recvp(rechan);
+	if(startinst == nil)
+		return FALSE;
+	optimize(program);
+	oprogp = progp;
+	backwards = TRUE;
+	threadcreate(realcompile, r, STACK);
+	bstartinst = recvp(rechan);
+	if(bstartinst == nil)
+		return FALSE;
+	optimize(oprogp);
+	lastregexp = runerealloc(lastregexp, nr);
+	runemove(lastregexp, r, nr);
+	return TRUE;
+}
+
+void
+operand(int t)
+{
+	Inst *i;
+	if(lastwasand)
+		operator(CAT);	/* catenate is implicit */
+	i = newinst(t);
+	if(t == CCLASS){
+		if(negateclass)
+			i->type = NCCLASS;	/* UGH */
+		i->u.class = nclass-1;		/* UGH */
+	}
+	pushand(i, i);
+	lastwasand = TRUE;
+}
+
+void
+operator(int t)
+{
+	if(t==RBRA && --nbra<0)
+		regerror("unmatched `)'");
+	if(t==LBRA){
+		cursubid++;	/* silently ignored */
+		nbra++;
+		if(lastwasand)
+			operator(CAT);
+	}else
+		evaluntil(t);
+	if(t!=RBRA)
+		pushator(t);
+	lastwasand = FALSE;
+	if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+		lastwasand = TRUE;	/* these look like operands */
+}
+
+void
+pushand(Inst *f, Inst *l)
+{
+	if(andp >= &andstack[NSTACK])
+		error("operand stack overflow");
+	andp->first = f;
+	andp->last = l;
+	andp++;
+}
+
+void
+pushator(int t)
+{
+	if(atorp >= &atorstack[NSTACK])
+		error("operator stack overflow");
+	*atorp++=t;
+	if(cursubid >= NRange)
+		*subidp++= -1;
+	else
+		*subidp++=cursubid;
+}
+
+Node *
+popand(int op)
+{
+	char buf[64];
+
+	if(andp <= &andstack[0])
+		if(op){
+			sprint(buf, "missing operand for %c", op);
+			regerror(buf);
+		}else
+			regerror("malformed regexp");
+	return --andp;
+}
+
+int
+popator()
+{
+	if(atorp <= &atorstack[0])
+		error("operator stack underflow");
+	--subidp;
+	return *--atorp;
+}
+
+void
+evaluntil(int pri)
+{
+	Node *op1, *op2, *t;
+	Inst *inst1, *inst2;
+
+	while(pri==RBRA || atorp[-1]>=pri){
+		switch(popator()){
+		case LBRA:
+			op1 = popand('(');
+			inst2 = newinst(RBRA);
+			inst2->u.subid = *subidp;
+			op1->last->u1.next = inst2;
+			inst1 = newinst(LBRA);
+			inst1->u.subid = *subidp;
+			inst1->u1.next = op1->first;
+			pushand(inst1, inst2);
+			return;		/* must have been RBRA */
+		default:
+			error("unknown regexp operator");
+			break;
+		case OR:
+			op2 = popand('|');
+			op1 = popand('|');
+			inst2 = newinst(NOP);
+			op2->last->u1.next = inst2;
+			op1->last->u1.next = inst2;
+			inst1 = newinst(OR);
+			inst1->u.right = op1->first;
+			inst1->u1.left = op2->first;
+			pushand(inst1, inst2);
+			break;
+		case CAT:
+			op2 = popand(0);
+			op1 = popand(0);
+			if(backwards && op2->first->type!=END){
+				t = op1;
+				op1 = op2;
+				op2 = t;
+			}
+			op1->last->u1.next = op2->first;
+			pushand(op1->first, op2->last);
+			break;
+		case STAR:
+			op2 = popand('*');
+			inst1 = newinst(OR);
+			op2->last->u1.next = inst1;
+			inst1->u.right = op2->first;
+			pushand(inst1, inst1);
+			break;
+		case PLUS:
+			op2 = popand('+');
+			inst1 = newinst(OR);
+			op2->last->u1.next = inst1;
+			inst1->u.right = op2->first;
+			pushand(op2->first, inst1);
+			break;
+		case QUEST:
+			op2 = popand('?');
+			inst1 = newinst(OR);
+			inst2 = newinst(NOP);
+			inst1->u1.left = inst2;
+			inst1->u.right = op2->first;
+			op2->last->u1.next = inst2;
+			pushand(inst1, inst2);
+			break;
+		}
+	}
+}
+
+
+void
+optimize(Inst *start)
+{
+	Inst *inst, *target;
+
+	for(inst=start; inst->type!=END; inst++){
+		target = inst->u1.next;
+		while(target->type == NOP)
+			target = target->u1.next;
+		inst->u1.next = target;
+	}
+}
+
+void
+startlex(Rune *s)
+{
+	exprp = s;
+	nbra = 0;
+}
+
+
+int
+lex(void){
+	int c;
+
+	c = *exprp++;
+	switch(c){
+	case '\\':
+		if(*exprp)
+			if((c= *exprp++)=='n')
+				c='\n';
+		break;
+	case 0:
+		c = END;
+		--exprp;	/* In case we come here again */
+		break;
+	case '*':
+		c = STAR;
+		break;
+	case '?':
+		c = QUEST;
+		break;
+	case '+':
+		c = PLUS;
+		break;
+	case '|':
+		c = OR;
+		break;
+	case '.':
+		c = ANY;
+		break;
+	case '(':
+		c = LBRA;
+		break;
+	case ')':
+		c = RBRA;
+		break;
+	case '^':
+		c = BOL;
+		break;
+	case '$':
+		c = EOL;
+		break;
+	case '[':
+		c = CCLASS;
+		bldcclass();
+		break;
+	}
+	return c;
+}
+
+int
+nextrec(void)
+{
+	if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
+		regerror("malformed `[]'");
+	if(exprp[0] == '\\'){
+		exprp++;
+		if(*exprp=='n'){
+			exprp++;
+			return '\n';
+		}
+		return *exprp++|0x10000;
+	}
+	return *exprp++;
+}
+
+void
+bldcclass(void)
+{
+	int c1, c2, n, na;
+	Rune *classp;
+
+	classp = runemalloc(DCLASS);
+	n = 0;
+	na = DCLASS;
+	/* we have already seen the '[' */
+	if(*exprp == '^'){
+		classp[n++] = '\n';	/* don't match newline in negate case */
+		negateclass = TRUE;
+		exprp++;
+	}else
+		negateclass = FALSE;
+	while((c1 = nextrec()) != ']'){
+		if(c1 == '-'){
+    Error:
+			free(classp);
+			regerror("malformed `[]'");
+		}
+		if(n+4 >= na){		/* 3 runes plus NUL */
+			na += DCLASS;
+			classp = runerealloc(classp, na);
+		}
+		if(*exprp == '-'){
+			exprp++;	/* eat '-' */
+			if((c2 = nextrec()) == ']')
+				goto Error;
+			classp[n+0] = 0xFFFF;
+			classp[n+1] = c1;
+			classp[n+2] = c2;
+			n += 3;
+		}else
+			classp[n++] = c1;
+	}
+	classp[n] = 0;
+	if(nclass == Nclass){
+		Nclass += DCLASS;
+		class = realloc(class, Nclass*sizeof(Rune*));
+	}
+	class[nclass++] = classp;
+}
+
+int
+classmatch(int classno, int c, int negate)
+{
+	Rune *p;
+
+	p = class[classno];
+	while(*p){
+		if(*p == 0xFFFF){
+			if(p[1]<=c && c<=p[2])
+				return !negate;
+			p += 3;
+		}else if(*p++ == c)
+			return !negate;
+	}
+	return negate;
+}
+
+/*
+ * Note optimization in addinst:
+ * 	*l must be pending when addinst called; if *l has been looked
+ *		at already, the optimization is a bug.
+ */
+void
+addinst(Ilist *l, Inst *inst, Rangeset *sep)
+{
+	Ilist *p;
+
+	for(p = l; p->inst; p++){
+		if(p->inst==inst){
+			if((sep)->r[0].q0 < p->se.r[0].q0)
+				p->se= *sep;	/* this would be bug */
+			return;	/* It's already there */
+		}
+	}
+	p->inst = inst;
+	p->se= *sep;
+	(p+1)->inst = nil;
+}
+
+int
+rxnull(void)
+{
+	return startinst==nil || bstartinst==nil;
+}
+
+/* either t!=nil or r!=nil, and we match the string in the appropriate place */
+int
+rxexecute(Text *t, Rune *r, uint startp, uint eof, Rangeset *rp)
+{
+	int flag;
+	Inst *inst;
+	Ilist *tlp;
+	uint p;
+	int nnl, ntl;
+	int nc, c;
+	int wrapped;
+	int startchar;
+
+	flag = 0;
+	p = startp;
+	startchar = 0;
+	wrapped = 0;
+	nnl = 0;
+	if(startinst->type<OPERATOR)
+		startchar = startinst->type;
+	list[0][0].inst = list[1][0].inst = nil;
+	sel.r[0].q0 = -1;
+	if(t != nil)
+		nc = t->file->b.nc;
+	else
+		nc = runestrlen(r);
+	/* Execute machine once for each character */
+	for(;;p++){
+	doloop:
+		if(p>=eof || p>=nc){
+			switch(wrapped++){
+			case 0:		/* let loop run one more click */
+			case 2:
+				break;
+			case 1:		/* expired; wrap to beginning */
+				if(sel.r[0].q0>=0 || eof!=Infinity)
+					goto Return;
+				list[0][0].inst = list[1][0].inst = nil;
+				p = 0;
+				goto doloop;
+			default:
+				goto Return;
+			}
+			c = 0;
+		}else{
+			if(((wrapped && p>=startp) || sel.r[0].q0>0) && nnl==0)
+				break;
+			if(t != nil)
+				c = textreadc(t, p);
+			else
+				c = r[p];
+		}
+		/* fast check for first char */
+		if(startchar && nnl==0 && c!=startchar)
+			continue;
+		tl = list[flag];
+		nl = list[flag^=1];
+		nl->inst = nil;
+		ntl = nnl;
+		nnl = 0;
+		if(sel.r[0].q0<0 && (!wrapped || p<startp || startp==eof)){
+			/* Add first instruction to this list */
+			if(++ntl >= NLIST){
+	Overflow:
+				warning(nil, "regexp list overflow\n");
+				sel.r[0].q0 = -1;
+				goto Return;
+			}
+			sempty.r[0].q0 = p;
+			addinst(tl, startinst, &sempty);
+		}
+		/* Execute machine until this list is empty */
+		for(tlp = tl; inst = tlp->inst; tlp++){	/* assignment = */
+	Switchstmt:
+			switch(inst->type){
+			default:	/* regular character */
+				if(inst->type==c){
+	Addinst:
+					if(++nnl >= NLIST)
+						goto Overflow;
+					addinst(nl, inst->u1.next, &tlp->se);
+				}
+				break;
+			case LBRA:
+				if(inst->u.subid>=0)
+					tlp->se.r[inst->u.subid].q0 = p;
+				inst = inst->u1.next;
+				goto Switchstmt;
+			case RBRA:
+				if(inst->u.subid>=0)
+					tlp->se.r[inst->u.subid].q1 = p;
+				inst = inst->u1.next;
+				goto Switchstmt;
+			case ANY:
+				if(c!='\n')
+					goto Addinst;
+				break;
+			case BOL:
+				if(p==0 || (t!=nil && textreadc(t, p-1)=='\n') || (r!=nil && r[p-1]=='\n')){
+	Step:
+					inst = inst->u1.next;
+					goto Switchstmt;
+				}
+				break;
+			case EOL:
+				if(c == '\n')
+					goto Step;
+				break;
+			case CCLASS:
+				if(c>=0 && classmatch(inst->u.class, c, 0))
+					goto Addinst;
+				break;
+			case NCCLASS:
+				if(c>=0 && classmatch(inst->u.class, c, 1))
+					goto Addinst;
+				break;
+			case OR:
+				/* evaluate right choice later */
+				if(++ntl >= NLIST)
+					goto Overflow;
+				addinst(tlp, inst->u.right, &tlp->se);
+				/* efficiency: advance and re-evaluate */
+				inst = inst->u1.left;
+				goto Switchstmt;
+			case END:	/* Match! */
+				tlp->se.r[0].q1 = p;
+				newmatch(&tlp->se);
+				break;
+			}
+		}
+	}
+    Return:
+	*rp = sel;
+	return sel.r[0].q0 >= 0;
+}
+
+void
+newmatch(Rangeset *sp)
+{
+	if(sel.r[0].q0<0 || sp->r[0].q0<sel.r[0].q0 ||
+	   (sp->r[0].q0==sel.r[0].q0 && sp->r[0].q1>sel.r[0].q1))
+		sel = *sp;
+}
+
+int
+rxbexecute(Text *t, uint startp, Rangeset *rp)
+{
+	int flag;
+	Inst *inst;
+	Ilist *tlp;
+	int p;
+	int nnl, ntl;
+	int c;
+	int wrapped;
+	int startchar;
+
+	flag = 0;
+	nnl = 0;
+	wrapped = 0;
+	p = startp;
+	startchar = 0;
+	if(bstartinst->type<OPERATOR)
+		startchar = bstartinst->type;
+	list[0][0].inst = list[1][0].inst = nil;
+	sel.r[0].q0= -1;
+	/* Execute machine once for each character, including terminal NUL */
+	for(;;--p){
+	doloop:
+		if(p <= 0){
+			switch(wrapped++){
+			case 0:		/* let loop run one more click */
+			case 2:
+				break;
+			case 1:		/* expired; wrap to end */
+				if(sel.r[0].q0>=0)
+					goto Return;
+				list[0][0].inst = list[1][0].inst = nil;
+				p = t->file->b.nc;
+				goto doloop;
+			case 3:
+			default:
+				goto Return;
+			}
+			c = 0;
+		}else{
+			if(((wrapped && p<=startp) || sel.r[0].q0>0) && nnl==0)
+				break;
+			c = textreadc(t, p-1);
+		}
+		/* fast check for first char */
+		if(startchar && nnl==0 && c!=startchar)
+			continue;
+		tl = list[flag];
+		nl = list[flag^=1];
+		nl->inst = nil;
+		ntl = nnl;
+		nnl = 0;
+		if(sel.r[0].q0<0 && (!wrapped || p>startp)){
+			/* Add first instruction to this list */
+			if(++ntl >= NLIST){
+	Overflow:
+				warning(nil, "regexp list overflow\n");
+				sel.r[0].q0 = -1;
+				goto Return;
+			}
+			/* the minus is so the optimizations in addinst work */
+			sempty.r[0].q0 = -p;
+			addinst(tl, bstartinst, &sempty);
+		}
+		/* Execute machine until this list is empty */
+		for(tlp = tl; inst = tlp->inst; tlp++){	/* assignment = */
+	Switchstmt:
+			switch(inst->type){
+			default:	/* regular character */
+				if(inst->type == c){
+	Addinst:
+					if(++nnl >= NLIST)
+						goto Overflow;
+					addinst(nl, inst->u1.next, &tlp->se);
+				}
+				break;
+			case LBRA:
+				if(inst->u.subid>=0)
+					tlp->se.r[inst->u.subid].q0 = p;
+				inst = inst->u1.next;
+				goto Switchstmt;
+			case RBRA:
+				if(inst->u.subid >= 0)
+					tlp->se.r[inst->u.subid].q1 = p;
+				inst = inst->u1.next;
+				goto Switchstmt;
+			case ANY:
+				if(c != '\n')
+					goto Addinst;
+				break;
+			case BOL:
+				if(c=='\n' || p==0){
+	Step:
+					inst = inst->u1.next;
+					goto Switchstmt;
+				}
+				break;
+			case EOL:
+				if(p<t->file->b.nc && textreadc(t, p)=='\n')
+					goto Step;
+				break;
+			case CCLASS:
+				if(c>0 && classmatch(inst->u.class, c, 0))
+					goto Addinst;
+				break;
+			case NCCLASS:
+				if(c>0 && classmatch(inst->u.class, c, 1))
+					goto Addinst;
+				break;
+			case OR:
+				/* evaluate right choice later */
+				if(++ntl >= NLIST)
+					goto Overflow;
+				addinst(tlp, inst->u.right, &tlp->se);
+				/* efficiency: advance and re-evaluate */
+				inst = inst->u1.left;
+				goto Switchstmt;
+			case END:	/* Match! */
+				tlp->se.r[0].q0 = -tlp->se.r[0].q0; /* minus sign */
+				tlp->se.r[0].q1 = p;
+				bnewmatch(&tlp->se);
+				break;
+			}
+		}
+	}
+    Return:
+	*rp = sel;
+	return sel.r[0].q0 >= 0;
+}
+
+void
+bnewmatch(Rangeset *sp)
+{
+        int  i;
+
+        if(sel.r[0].q0<0 || sp->r[0].q0>sel.r[0].q1 || (sp->r[0].q0==sel.r[0].q1 && sp->r[0].q1<sel.r[0].q0))
+                for(i = 0; i<NRange; i++){       /* note the reversal; q0<=q1 */
+                        sel.r[i].q0 = sp->r[i].q1;
+                        sel.r[i].q1 = sp->r[i].q0;
+                }
+}
blob - /dev/null
blob + 11014c2cdbacd5d5b7e9069a2597448b946db42f (mode 644)
--- /dev/null
+++ src/cmd/acme/rows.c
@@ -0,0 +1,731 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <bio.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Rune Lcolhdr[] = {
+	'N', 'e', 'w', 'c', 'o', 'l', ' ',
+	'K', 'i', 'l', 'l', ' ',
+	'P', 'u', 't', 'a', 'l', 'l', ' ',
+	'D', 'u', 'm', 'p', ' ',
+	'E', 'x', 'i', 't', ' ',
+	0
+};
+
+void
+rowinit(Row *row, Rectangle r)
+{
+	Rectangle r1;
+	Text *t;
+
+	draw(screen, r, display->white, nil, ZP);
+	row->r = r;
+	row->col = nil;
+	row->ncol = 0;
+	r1 = r;
+	r1.max.y = r1.min.y + font->height;
+	t = &row->tag;
+	textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
+	t->what = Rowtag;
+	t->row = row;
+	t->w = nil;
+	t->col = nil;
+	r1.min.y = r1.max.y;
+	r1.max.y += Border;
+	draw(screen, r1, display->black, nil, ZP);
+	textinsert(t, 0, Lcolhdr, 29, TRUE);
+	textsetselect(t, t->file->b.nc, t->file->b.nc);
+}
+
+Column*
+rowadd(Row *row, Column *c, int x)
+{
+	Rectangle r, r1;
+	Column *d;
+	int i;
+
+	d = nil;
+	r = row->r;
+	r.min.y = row->tag.fr.r.max.y+Border;
+	if(x<r.min.x && row->ncol>0){	/*steal 40% of last column by default */
+		d = row->col[row->ncol-1];
+		x = d->r.min.x + 3*Dx(d->r)/5;
+	}
+	/* look for column we'll land on */
+	for(i=0; i<row->ncol; i++){
+		d = row->col[i];
+		if(x < d->r.max.x)
+			break;
+	}
+	if(row->ncol > 0){
+		if(i < row->ncol)
+			i++;	/* new column will go after d */
+		r = d->r;
+		if(Dx(r) < 100)
+			return nil;
+		draw(screen, r, display->white, nil, ZP);
+		r1 = r;
+		r1.max.x = min(x, r.max.x-50);
+		if(Dx(r1) < 50)
+			r1.max.x = r1.min.x+50;
+		colresize(d, r1);
+		r1.min.x = r1.max.x;
+		r1.max.x = r1.min.x+Border;
+		draw(screen, r1, display->black, nil, ZP);
+		r.min.x = r1.max.x;
+	}
+	if(c == nil){
+		c = emalloc(sizeof(Column));
+		colinit(c, r);
+		incref(&reffont.ref);
+	}else
+		colresize(c, r);
+	c->row = row;
+	c->tag.row = row;
+	row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
+	memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
+	row->col[i] = c;
+	row->ncol++;
+	clearmouse();
+	return c;
+}
+
+void
+rowresize(Row *row, Rectangle r)
+{
+	int i, dx, odx;
+	Rectangle r1, r2;
+	Column *c;
+
+	dx = Dx(r);
+	odx = Dx(row->r);
+	row->r = r;
+	r1 = r;
+	r1.max.y = r1.min.y + font->height;
+	textresize(&row->tag, r1);
+	r1.min.y = r1.max.y;
+	r1.max.y += Border;
+	draw(screen, r1, display->black, nil, ZP);
+	r.min.y = r1.max.y;
+	r1 = r;
+	r1.max.x = r1.min.x;
+	for(i=0; i<row->ncol; i++){
+		c = row->col[i];
+		r1.min.x = r1.max.x;
+		if(i == row->ncol-1)
+			r1.max.x = r.max.x;
+		else
+			r1.max.x = r1.min.x+Dx(c->r)*dx/odx;
+		if(i > 0){
+			r2 = r1;
+			r2.max.x = r2.min.x+Border;
+			draw(screen, r2, display->black, nil, ZP);
+			r1.min.x = r2.max.x;
+		}
+		colresize(c, r1);
+	}
+}
+
+void
+rowdragcol(Row *row, Column *c, int _0)
+{
+	Rectangle r;
+	int i, b, x;
+	Point p, op;
+	Column *d;
+
+	USED(_0);
+
+	clearmouse();
+	setcursor(mousectl, &boxcursor);
+	b = mouse->buttons;
+	op = mouse->xy;
+	while(mouse->buttons == b)
+		readmouse(mousectl);
+	setcursor(mousectl, nil);
+	if(mouse->buttons){
+		while(mouse->buttons)
+			readmouse(mousectl);
+		return;
+	}
+
+	for(i=0; i<row->ncol; i++)
+		if(row->col[i] == c)
+			goto Found;
+	error("can't find column");
+
+  Found:
+	if(i == 0)
+		return;
+	p = mouse->xy;
+	if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
+		return;
+	if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
+		/* shuffle */
+		x = c->r.min.x;
+		rowclose(row, c, FALSE);
+		if(rowadd(row, c, p.x) == nil)	/* whoops! */
+		if(rowadd(row, c, x) == nil)		/* WHOOPS! */
+		if(rowadd(row, c, -1)==nil){		/* shit! */
+			rowclose(row, c, TRUE);
+			return;
+		}
+		colmousebut(c);
+		return;
+	}
+	d = row->col[i-1];
+	if(p.x < d->r.min.x+80+Scrollwid)
+		p.x = d->r.min.x+80+Scrollwid;
+	if(p.x > c->r.max.x-80-Scrollwid)
+		p.x = c->r.max.x-80-Scrollwid;
+	r = d->r;
+	r.max.x = c->r.max.x;
+	draw(screen, r, display->white, nil, ZP);
+	r.max.x = p.x;
+	colresize(d, r);
+	r = c->r;
+	r.min.x = p.x;
+	r.max.x = r.min.x;
+	r.max.x += Border;
+	draw(screen, r, display->black, nil, ZP);
+	r.min.x = r.max.x;
+	r.max.x = c->r.max.x;
+	colresize(c, r);
+	colmousebut(c);
+}
+
+void
+rowclose(Row *row, Column *c, int dofree)
+{
+	Rectangle r;
+	int i;
+
+	for(i=0; i<row->ncol; i++)
+		if(row->col[i] == c)
+			goto Found;
+	error("can't find column");
+  Found:
+	r = c->r;
+	if(dofree)
+		colcloseall(c);
+	memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
+	row->ncol--;
+	row->col = realloc(row->col, row->ncol*sizeof(Column*));
+	if(row->ncol == 0){
+		draw(screen, r, display->white, nil, ZP);
+		return;
+	}
+	if(i == row->ncol){		/* extend last column right */
+		c = row->col[i-1];
+		r.min.x = c->r.min.x;
+		r.max.x = row->r.max.x;
+	}else{			/* extend next window left */
+		c = row->col[i];
+		r.max.x = c->r.max.x;
+	}
+	draw(screen, r, display->white, nil, ZP);
+	colresize(c, r);
+}
+
+Column*
+rowwhichcol(Row *row, Point p)
+{
+	int i;
+	Column *c;
+
+	for(i=0; i<row->ncol; i++){
+		c = row->col[i];
+		if(ptinrect(p, c->r))
+			return c;
+	}
+	return nil;
+}
+
+Text*
+rowwhich(Row *row, Point p)
+{
+	Column *c;
+
+	if(ptinrect(p, row->tag.all))
+		return &row->tag;
+	c = rowwhichcol(row, p);
+	if(c)
+		return colwhich(c, p);
+	return nil;
+}
+
+Text*
+rowtype(Row *row, Rune r, Point p)
+{
+	Window *w;
+	Text *t;
+
+	clearmouse();
+	qlock(&row->lk);
+	if(bartflag)
+		t = barttext;
+	else
+		t = rowwhich(row, p);
+	if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
+		w = t->w;
+		if(w == nil)
+			texttype(t, r);
+		else{
+			winlock(w, 'K');
+			wintype(w, t, r);
+			winunlock(w);
+		}
+	}
+	qunlock(&row->lk);
+	return t;
+}
+
+int
+rowclean(Row *row)
+{
+	int clean;
+	int i;
+
+	clean = TRUE;
+	for(i=0; i<row->ncol; i++)
+		clean &= colclean(row->col[i]);
+	return clean;
+}
+
+void
+rowdump(Row *row, char *file)
+{
+	int i, j, fd, m, n, dumped;
+	uint q0, q1;
+	Biobuf *b;
+	char *buf, *a, *fontname;
+	Rune *r;
+	Column *c;
+	Window *w, *w1;
+	Text *t;
+
+	if(row->ncol == 0)
+		return;
+	buf = fbufalloc();
+	if(file == nil){
+		if(home == nil){
+			warning(nil, "can't find file for dump: $home not defined\n");
+			goto Rescue;
+		}
+		sprint(buf, "%s/acme.dump", home);
+		file = buf;
+	}
+	fd = create(file, OWRITE, 0600);
+	if(fd < 0){
+		warning(nil, "can't open %s: %r\n", file);
+		goto Rescue;
+	}
+	b = emalloc(sizeof(Biobuf));
+	Binit(b, fd, OWRITE);
+	r = fbufalloc();
+	Bprint(b, "%s\n", wdir);
+	Bprint(b, "%s\n", fontnames[0]);
+	Bprint(b, "%s\n", fontnames[1]);
+	for(i=0; i<row->ncol; i++){
+		c = row->col[i];
+		Bprint(b, "%11d", 100*(c->r.min.x-row->r.min.x)/Dx(row->r));
+		if(i == row->ncol-1)
+			Bputc(b, '\n');
+		else
+			Bputc(b, ' ');
+	}
+	for(i=0; i<row->ncol; i++){
+		c = row->col[i];
+		for(j=0; j<c->nw; j++)
+			c->w[j]->body.file->dumpid = 0;
+	}
+	for(i=0; i<row->ncol; i++){
+		c = row->col[i];
+		for(j=0; j<c->nw; j++){
+			w = c->w[j];
+			wincommit(w, &w->tag);
+			t = &w->body;
+			/* windows owned by others get special treatment */
+			if(w->nopen[QWevent] > 0)
+				if(w->dumpstr == nil)
+					continue;
+			/* zeroxes of external windows are tossed */
+			if(t->file->ntext > 1)
+				for(n=0; n<t->file->ntext; n++){
+					w1 = t->file->text[n]->w;
+					if(w == w1)
+						continue;
+					if(w1->nopen[QWevent])
+						goto Continue2;
+				}
+			fontname = "";
+			if(t->reffont->f != font)
+				fontname = t->reffont->f->name;
+			if(t->file->nname)
+				a = runetobyte(t->file->name, t->file->nname);
+			else
+				a = emalloc(1);
+			if(t->file->dumpid){
+				dumped = FALSE;
+				Bprint(b, "x%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
+					w->body.q0, w->body.q1,
+					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+					fontname);
+			}else if(w->dumpstr){
+				dumped = FALSE;
+				Bprint(b, "e%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
+					0, 0,
+					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+					fontname);
+			}else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
+				dumped = FALSE;
+				t->file->dumpid = w->id;
+				Bprint(b, "f%11d %11d %11d %11d %11d %s\n", i, w->id,
+					w->body.q0, w->body.q1,
+					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+					fontname);
+			}else{
+				dumped = TRUE;
+				t->file->dumpid = w->id;
+				Bprint(b, "F%11d %11d %11d %11d %11d %11d %s\n", i, j,
+					w->body.q0, w->body.q1,
+					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
+					w->body.file->b.nc, fontname);
+			}
+			free(a);
+			winctlprint(w, buf, 0);
+			Bwrite(b, buf, strlen(buf));
+			m = min(RBUFSIZE, w->tag.file->b.nc);
+			bufread(&w->tag.file->b, 0, r, m);
+			n = 0;
+			while(n<m && r[n]!='\n')
+				n++;
+			r[n++] = '\n';
+			Bprint(b, "%.*S", n, r);
+			if(dumped){
+				q0 = 0;
+				q1 = t->file->b.nc;
+				while(q0 < q1){
+					n = q1 - q0;
+					if(n > BUFSIZE/UTFmax)
+						n = BUFSIZE/UTFmax;
+					bufread(&t->file->b, q0, r, n);
+					Bprint(b, "%.*S", n, r);
+					q0 += n;
+				}
+			}
+			if(w->dumpstr){
+				if(w->dumpdir)
+					Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
+				else
+					Bprint(b, "\n%s\n", w->dumpstr);
+			}
+    Continue2:;
+		}
+	}
+	Bterm(b);
+	close(fd);
+	free(b);
+	fbuffree(r);
+
+   Rescue:
+	fbuffree(buf);
+}
+
+static
+char*
+rdline(Biobuf *b, int *linep)
+{
+	char *l;
+
+	l = Brdline(b, '\n');
+	if(l)
+		(*linep)++;
+	return l;
+}
+
+/*
+ * Get font names from load file so we don't load fonts we won't use
+ */
+void
+rowloadfonts(char *file)
+{
+	int i;
+	Biobuf *b;
+	char *l;
+
+	b = Bopen(file, OREAD);
+	if(b == nil)
+		return;
+	/* current directory */
+	l = Brdline(b, '\n');
+	if(l == nil)
+		goto Return;
+	/* global fonts */
+	for(i=0; i<2; i++){
+		l = Brdline(b, '\n');
+		if(l == nil)
+			goto Return;
+		l[Blinelen(b)-1] = 0;
+		if(*l && strcmp(l, fontnames[i])!=0)
+			fontnames[i] = estrdup(l);
+	}
+    Return:
+	Bterm(b);
+}
+
+void
+rowload(Row *row, char *file, int initing)
+{
+	int i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd;
+	Biobuf *b, *bout;
+	char *buf, *l, *t, *fontname;
+	Rune *r, rune, *fontr;
+	Column *c, *c1, *c2;
+	uint q0, q1;
+	Rectangle r1, r2;
+	Window *w;
+
+	buf = fbufalloc();
+	if(file == nil){
+		if(home == nil){
+			warning(nil, "can't find file for load: $home not defined\n");
+			goto Rescue1;
+		}
+		sprint(buf, "%s/acme.dump", home);
+		file = buf;
+	}
+	b = Bopen(file, OREAD);
+	if(b == nil){
+		warning(nil, "can't open load file %s: %r\n", file);
+		goto Rescue1;
+	}
+	/* current directory */
+	line = 0;
+	l = rdline(b, &line);
+	if(l == nil)
+		goto Rescue2;
+	l[Blinelen(b)-1] = 0;
+	if(chdir(l) < 0){
+		warning(nil, "can't chdir %s\n", l);
+		goto Rescue2;
+	}
+	/* global fonts */
+	for(i=0; i<2; i++){
+		l = rdline(b, &line);
+		if(l == nil)
+			goto Rescue2;
+		l[Blinelen(b)-1] = 0;
+		if(*l && strcmp(l, fontnames[i])!=0)
+			rfget(i, TRUE, i==0 && initing, estrdup(l));
+	}
+	if(initing && row->ncol==0)
+		rowinit(row, screen->clipr);
+	l = rdline(b, &line);
+	if(l == nil)
+		goto Rescue2;
+	j = Blinelen(b)/12;
+	if(j<=0 || j>10)
+		goto Rescue2;
+	for(i=0; i<j; i++){
+		percent = atoi(l+i*12);
+		if(percent<0 || percent>=100)
+			goto Rescue2;
+		x = row->r.min.x+percent*Dx(row->r)/100;
+		if(i < row->ncol){
+			if(i == 0)
+				continue;
+			c1 = row->col[i-1];
+			c2 = row->col[i];
+			r1 = c1->r;
+			r2 = c2->r;
+			r1.max.x = x;
+			r2.min.x = x+Border;
+			if(Dx(r1) < 50 || Dx(r2) < 50)
+				continue;
+			draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP);
+			colresize(c1, r1);
+			colresize(c2, r2);
+			r2.min.x = x;
+			r2.max.x = x+Border;
+			draw(screen, r2, display->black, nil, ZP);
+		}
+		if(i >= row->ncol)
+			rowadd(row, nil, x);
+	}
+	for(;;){
+		l = rdline(b, &line);
+		if(l == nil)
+			break;
+		dumpid = 0;
+		switch(l[0]){
+		case 'e':
+			if(Blinelen(b) < 1+5*12+1)
+				goto Rescue2;
+			l = rdline(b, &line);	/* ctl line; ignored */
+			if(l == nil)
+				goto Rescue2;
+			l = rdline(b, &line);	/* directory */
+			if(l == nil)
+				goto Rescue2;
+			l[Blinelen(b)-1] = 0;
+			if(*l == '\0'){
+				if(home == nil)
+					r = bytetorune("./", &nr);
+				else{
+					t = emalloc(strlen(home)+1+1);
+					sprint(t, "%s/", home);
+					r = bytetorune(t, &nr);
+					free(t);
+				}
+			}else
+				r = bytetorune(l, &nr);
+			l = rdline(b, &line);	/* command */
+			if(l == nil)
+				goto Rescue2;
+			t = emalloc(Blinelen(b)+1);
+			memmove(t, l, Blinelen(b));
+			run(nil, t, r, nr, TRUE, nil, nil, FALSE);
+			/* r is freed in run() */
+			continue;
+		case 'f':
+			if(Blinelen(b) < 1+5*12+1)
+				goto Rescue2;
+			fontname = l+1+5*12;
+			ndumped = -1;
+			break;
+		case 'F':
+			if(Blinelen(b) < 1+6*12+1)
+				goto Rescue2;
+			fontname = l+1+6*12;
+			ndumped = atoi(l+1+5*12+1);
+			break;
+		case 'x':
+			if(Blinelen(b) < 1+5*12+1)
+				goto Rescue2;
+			fontname = l+1+5*12;
+			ndumped = -1;
+			dumpid = atoi(l+1+1*12);
+			break;
+		default:
+			goto Rescue2;
+		}
+		l[Blinelen(b)-1] = 0;
+		fontr = nil;
+		nfontr = 0;
+		if(*fontname)
+			fontr = bytetorune(fontname, &nfontr);
+		i = atoi(l+1+0*12);
+		j = atoi(l+1+1*12);
+		q0 = atoi(l+1+2*12);
+		q1 = atoi(l+1+3*12);
+		percent = atoi(l+1+4*12);
+		if(i<0 || i>10)
+			goto Rescue2;
+		if(i > row->ncol)
+			i = row->ncol;
+		c = row->col[i];
+		y = c->r.min.y+(percent*Dy(c->r))/100;
+		if(y<c->r.min.y || y>=c->r.max.y)
+			y = -1;
+		if(dumpid == 0)
+			w = coladd(c, nil, nil, y);
+		else
+			w = coladd(c, nil, lookid(dumpid, TRUE), y);
+		if(w == nil)
+			continue;
+		w->dumpid = j;
+		l = rdline(b, &line);
+		if(l == nil)
+			goto Rescue2;
+		l[Blinelen(b)-1] = 0;
+		r = bytetorune(l+5*12, &nr);
+		ns = -1;
+		for(n=0; n<nr; n++){
+			if(r[n] == '/')
+				ns = n;
+			if(r[n] == ' ')
+				break;
+		}
+		if(dumpid == 0)
+			winsetname(w, r, n);
+		for(; n<nr; n++)
+			if(r[n] == '|')
+				break;
+		wincleartag(w);
+		textinsert(&w->tag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE);
+		free(r);
+		if(ndumped >= 0){
+			/* simplest thing is to put it in a file and load that */
+			sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser());
+			fd = create(buf, OWRITE|ORCLOSE, 0600);
+			if(fd < 0){
+				warning(nil, "can't create temp file: %r\n");
+				goto Rescue2;
+			}
+			bout = emalloc(sizeof(Biobuf));
+			Binit(bout, fd, OWRITE);
+			for(n=0; n<ndumped; n++){
+				rune = Bgetrune(b);
+				if(rune == '\n')
+					line++;
+				if(rune == (Rune)Beof){
+					Bterm(bout);
+					free(bout);
+					close(fd);
+					goto Rescue2;
+				}
+				Bputrune(bout, rune);
+			}
+			Bterm(bout);
+			free(bout);
+			textload(&w->body, 0, buf, 1);
+			close(fd);
+			w->body.file->mod = TRUE;
+			for(n=0; n<w->body.file->ntext; n++)
+				w->body.file->text[n]->w->dirty = TRUE;
+			winsettag(w);
+		}else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
+			get(&w->body, nil, nil, FALSE, XXX, nil, 0);
+		if(fontr){
+			fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
+			free(fontr);
+		}
+		if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1)
+			q0 = q1 = 0;
+		textshow(&w->body, q0, q1, 1);
+		w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
+	}
+	Bterm(b);
+
+Rescue1:
+	fbuffree(buf);
+	return;
+
+Rescue2:
+	warning(nil, "bad load file %s:%d\n", file, line);
+	Bterm(b);
+	goto Rescue1;
+}
+
+void
+allwindows(void (*f)(Window*, void*), void *arg)
+{
+	int i, j;
+	Column *c;
+
+	for(i=0; i<row.ncol; i++){
+		c = row.col[i];
+		for(j=0; j<c->nw; j++)
+			(*f)(c->w[j], arg);
+	}
+}
blob - /dev/null
blob + 77aa0f856079394d3437b881ea053ab2cfabf01b (mode 644)
--- /dev/null
+++ src/cmd/acme/scrl.c
@@ -0,0 +1,165 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Image *scrtmp;
+
+static
+Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+	Rectangle q;
+	int h;
+
+	q = r;
+	h = q.max.y-q.min.y;
+	if(tot == 0)
+		return q;
+	if(tot > 1024*1024){
+		tot>>=10;
+		p0>>=10;
+		p1>>=10;
+	}
+	if(p0 > 0)
+		q.min.y += h*p0/tot;
+	if(p1 < tot)
+		q.max.y -= h*(tot-p1)/tot;
+	if(q.max.y < q.min.y+2){
+		if(q.min.y+2 <= r.max.y)
+			q.max.y = q.min.y+2;
+		else
+			q.min.y = q.max.y-2;
+	}
+	return q;
+}
+
+void
+scrlresize(void)
+{
+	freeimage(scrtmp);
+	scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill);
+	if(scrtmp == nil)
+		error("scroll alloc");
+}
+
+void
+textscrdraw(Text *t)
+{
+	Rectangle r, r1, r2;
+	Image *b;
+
+	if(t->w==nil || t!=&t->w->body)
+		return;
+	if(scrtmp == nil)
+		scrlresize();
+	r = t->scrollr;
+	b = scrtmp;
+	r1 = r;
+	r1.min.x = 0;
+	r1.max.x = Dx(r);
+	r2 = scrpos(r1, t->org, t->org+t->fr.nchars, t->file->b.nc);
+	if(!eqrect(r2, t->lastsr)){
+		t->lastsr = r2;
+		draw(b, r1, t->fr.cols[BORD], nil, ZP);
+		draw(b, r2, t->fr.cols[BACK], nil, ZP);
+		r2.min.x = r2.max.x-1;
+		draw(b, r2, t->fr.cols[BORD], nil, ZP);
+		draw(t->fr.b, r, b, nil, Pt(0, r1.min.y));
+/*flushimage(display, 1);*//*BUG?*/
+	}
+}
+
+void
+scrsleep(uint dt)
+{
+	Timer	*timer;
+	static Alt alts[3];
+
+	timer = timerstart(dt);
+	alts[0].c = timer->c;
+	alts[0].v = nil;
+	alts[0].op = CHANRCV;
+	alts[1].c = mousectl->c;
+	alts[1].v = &mousectl->m;
+	alts[1].op = CHANRCV;
+	alts[2].op = CHANEND;
+	for(;;)
+		switch(alt(alts)){
+		case 0:
+			timerstop(timer);
+			return;
+		case 1:
+			timercancel(timer);
+			return;
+		}
+}
+
+void
+textscroll(Text *t, int but)
+{
+	uint p0, oldp0;
+	Rectangle s;
+	int x, y, my, h, first;
+
+	s = insetrect(t->scrollr, 1);
+	h = s.max.y-s.min.y;
+	x = (s.min.x+s.max.x)/2;
+	oldp0 = ~0;
+	first = TRUE;
+	do{
+		flushimage(display, 1);
+		if(mouse->xy.x<s.min.x || s.max.x<=mouse->xy.x){
+			readmouse(mousectl);
+		}else{
+			my = mouse->xy.y;
+			if(my < s.min.y)
+				my = s.min.y;
+			if(my >= s.max.y)
+				my = s.max.y;
+			if(!eqpt(mouse->xy, Pt(x, my))){
+				moveto(mousectl, Pt(x, my));
+				readmouse(mousectl);		/* absorb event generated by moveto() */
+			}
+			if(but == 2){
+				y = my;
+				if(y > s.max.y-2)
+					y = s.max.y-2;
+				if(t->file->b.nc > 1024*1024)
+					p0 = ((t->file->b.nc>>10)*(y-s.min.y)/h)<<10;
+				else
+					p0 = t->file->b.nc*(y-s.min.y)/h;
+				if(oldp0 != p0)
+					textsetorigin(t, p0, FALSE);
+				oldp0 = p0;
+				readmouse(mousectl);
+				continue;
+			}
+			if(but == 1)
+				p0 = textbacknl(t, t->org, (my-s.min.y)/t->fr.font->height);
+			else
+				p0 = t->org+frcharofpt(&t->fr, Pt(s.max.x, my));
+			if(oldp0 != p0)
+				textsetorigin(t, p0, TRUE);
+			oldp0 = p0;
+			/* debounce */
+			if(first){
+				flushimage(display, 1);
+				sleep(200);
+				nbrecv(mousectl->c, &mousectl->m);
+				first = FALSE;
+			}
+			scrsleep(80);
+		}
+	}while(mouse->buttons & (1<<(but-1)));
+	while(mouse->buttons)
+		readmouse(mousectl);
+}
blob - /dev/null
blob + c38773ea9e86cd717de27d424c0780a10d226d14 (mode 644)
--- /dev/null
+++ src/cmd/acme/text.c
@@ -0,0 +1,1221 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+Image	*tagcols[NCOL];
+Image	*textcols[NCOL];
+
+enum{
+	TABDIR = 3	/* width of tabs in directory windows */
+};
+
+void
+textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
+{
+	t->file = f;
+	t->all = r;
+	t->scrollr = r;
+	t->scrollr.max.x = r.min.x+Scrollwid;
+	t->lastsr = nullrect;
+	r.min.x += Scrollwid+Scrollgap;
+	t->eq0 = ~0;
+	t->ncache = 0;
+	t->reffont = rf;
+	t->tabstop = maxtab;
+	memmove(t->fr.cols, cols, sizeof t->fr.cols);
+	textredraw(t, r, rf->f, screen, -1);
+}
+
+void
+textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
+{
+	int maxt;
+	Rectangle rr;
+
+	frinit(&t->fr, r, f, b, t->fr.cols);
+	rr = t->fr.r;
+	rr.min.x -= Scrollwid;	/* back fill to scroll bar */
+	draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
+	/* use no wider than 3-space tabs in a directory */
+	maxt = maxtab;
+	if(t->what == Body){
+		if(t->w->isdir)
+			maxt = min(TABDIR, maxtab);
+		else
+			maxt = t->tabstop;
+	}
+	t->fr.maxtab = maxt*stringwidth(f, "0");
+	if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
+		if(t->fr.maxlines > 0){
+			textreset(t);
+			textcolumnate(t, t->w->dlp,  t->w->ndl);
+			textshow(t, 0, 0, 1);
+		}
+	}else{
+		textfill(t);
+		textsetselect(t, t->q0, t->q1);
+	}
+}
+
+int
+textresize(Text *t, Rectangle r)
+{
+	int odx;
+
+	if(Dy(r) > 0)
+		r.max.y -= Dy(r)%t->fr.font->height;
+	else
+		r.max.y = r.min.y;
+	odx = Dx(t->all);
+	t->all = r;
+	t->scrollr = r;
+	t->scrollr.max.x = r.min.x+Scrollwid;
+	t->lastsr = nullrect;
+	r.min.x += Scrollwid+Scrollgap;
+	frclear(&t->fr, 0);
+	textredraw(t, r, t->fr.font, t->fr.b, odx);
+	return r.max.y;
+}
+
+void
+textclose(Text *t)
+{
+	free(t->cache);
+	frclear(&t->fr, 1);
+	filedeltext(t->file, t);
+	t->file = nil;
+	rfclose(t->reffont);
+	if(argtext == t)
+		argtext = nil;
+	if(typetext == t)
+		typetext = nil;
+	if(seltext == t)
+		seltext = nil;
+	if(mousetext == t)
+		mousetext = nil;
+	if(barttext == t)
+		barttext = nil;
+}
+
+int
+dircmp(const void *a, const void *b)
+{
+	Dirlist *da, *db;
+	int i, n;
+
+	da = *(Dirlist**)a;
+	db = *(Dirlist**)b;
+	n = min(da->nr, db->nr);
+	i = memcmp(da->r, db->r, n*sizeof(Rune));
+	if(i)
+		return i;
+	return da->nr - db->nr;
+}
+
+void
+textcolumnate(Text *t, Dirlist **dlp, int ndl)
+{
+	int i, j, w, colw, mint, maxt, ncol, nrow;
+	Dirlist *dl;
+	uint q1;
+	static Rune Lnl[] = { '\n', 0 };
+	static Rune Ltab[] = { '\t', 0 };
+
+	if(t->file->ntext > 1)
+		return;
+	mint = stringwidth(t->fr.font, "0");
+	/* go for narrower tabs if set more than 3 wide */
+	t->fr.maxtab = min(maxtab, TABDIR)*mint;
+	maxt = t->fr.maxtab;
+	colw = 0;
+	for(i=0; i<ndl; i++){
+		dl = dlp[i];
+		w = dl->wid;
+		if(maxt-w%maxt < mint || w%maxt==0)
+			w += mint;
+		if(w % maxt)
+			w += maxt-(w%maxt);
+		if(w > colw)
+			colw = w;
+	}
+	if(colw == 0)
+		ncol = 1;
+	else
+		ncol = max(1, Dx(t->fr.r)/colw);
+	nrow = (ndl+ncol-1)/ncol;
+
+	q1 = 0;
+	for(i=0; i<nrow; i++){
+		for(j=i; j<ndl; j+=nrow){
+			dl = dlp[j];
+			fileinsert(t->file, q1, dl->r, dl->nr);
+			q1 += dl->nr;
+			if(j+nrow >= ndl)
+				break;
+			w = dl->wid;
+			if(maxt-w%maxt < mint){
+				fileinsert(t->file, q1, Ltab, 1);
+				q1++;
+				w += mint;
+			}
+			do{
+				fileinsert(t->file, q1, Ltab, 1);
+				q1++;
+				w += maxt-(w%maxt);
+			}while(w < colw);
+		}
+		fileinsert(t->file, q1, Lnl, 1);
+		q1++;
+	}
+}
+
+uint
+textload(Text *t, uint q0, char *file, int setqid)
+{
+	Rune *rp;
+	Dirlist *dl, **dlp;
+	int fd, i, j, n, ndl, nulls;
+	uint q, q1;
+	Dir *d, *dbuf;
+	char *tmp;
+	Text *u;
+
+	if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body || (t->w->isdir && t->file->nname==0))
+		error("text.load");
+	fd = open(file, OREAD);
+	if(fd < 0){
+		warning(nil, "can't open %s: %r\n", file);
+		return 0;
+	}
+	d = dirfstat(fd);
+	if(d == nil){
+		warning(nil, "can't fstat %s: %r\n", file);
+		goto Rescue;
+	}
+	nulls = FALSE;
+	if(d->qid.type & QTDIR){
+		/* this is checked in get() but it's possible the file changed underfoot */
+		if(t->file->ntext > 1){
+			warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
+			goto Rescue;
+		}
+		t->w->isdir = TRUE;
+		t->w->filemenu = FALSE;
+		if(t->file->name[t->file->nname-1] != '/'){
+			rp = runemalloc(t->file->nname+1);
+			runemove(rp, t->file->name, t->file->nname);
+			rp[t->file->nname] = '/';
+			winsetname(t->w, rp, t->file->nname+1);
+			free(rp);
+		}
+		dlp = nil;
+		ndl = 0;
+		dbuf = nil;
+		while((n=dirread(fd, &dbuf)) > 0){
+			for(i=0; i<n; i++){
+				dl = emalloc(sizeof(Dirlist));
+				j = strlen(dbuf[i].name);
+				tmp = emalloc(j+1+1);
+				memmove(tmp, dbuf[i].name, j);
+				if(dbuf[i].qid.type & QTDIR)
+					tmp[j++] = '/';
+				tmp[j] = '\0';
+				dl->r = bytetorune(tmp, &dl->nr);
+				dl->wid = stringwidth(t->fr.font, tmp);
+				free(tmp);
+				ndl++;
+				dlp = realloc(dlp, ndl*sizeof(Dirlist*));
+				dlp[ndl-1] = dl;
+			}
+			free(dbuf);
+		}
+		qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
+		t->w->dlp = dlp;
+		t->w->ndl = ndl;
+		textcolumnate(t, dlp, ndl);
+		q1 = t->file->b.nc;
+	}else{
+		t->w->isdir = FALSE;
+		t->w->filemenu = TRUE;
+		q1 = q0 + fileload(t->file, q0, fd, &nulls);
+	}
+	if(setqid){
+		t->file->dev = d->dev;
+		t->file->mtime = d->mtime;
+		t->file->qidpath = d->qid.path;
+	}
+	close(fd);
+	rp = fbufalloc();
+	for(q=q0; q<q1; q+=n){
+		n = q1-q;
+		if(n > RBUFSIZE)
+			n = RBUFSIZE;
+		bufread(&t->file->b, q, rp, n);
+		if(q < t->org)
+			t->org += n;
+		else if(q <= t->org+t->fr.nchars)
+			frinsert(&t->fr, rp, rp+n, q-t->org);
+		if(t->fr.lastlinefull)
+			break;
+	}
+	fbuffree(rp);
+	for(i=0; i<t->file->ntext; i++){
+		u = t->file->text[i];
+		if(u != t){
+			if(u->org > u->file->b.nc)	/* will be 0 because of reset(), but safety first */
+				u->org = 0;
+			textresize(u, u->all);
+			textbacknl(u, u->org, 0);	/* go to beginning of line */
+		}
+		textsetselect(u, q0, q0);
+	}
+	if(nulls)
+		warning(nil, "%s: NUL bytes elided\n", file);
+	free(d);
+	return q1-q0;
+
+    Rescue:
+	close(fd);
+	return 0;
+}
+
+uint
+textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
+{
+	Rune *bp, *tp, *up;
+	int i, initial;
+
+	if(t->what == Tag){	/* can't happen but safety first: mustn't backspace over file name */
+    Err:
+		textinsert(t, q0, r, n, tofile);
+		*nrp = n;
+		return q0;
+	}
+	bp = r;
+	for(i=0; i<n; i++)
+		if(*bp++ == '\b'){
+			--bp;
+			initial = 0;
+			tp = runemalloc(n);
+			runemove(tp, r, i);
+			up = tp+i;
+			for(; i<n; i++){
+				*up = *bp++;
+				if(*up == '\b')
+					if(up == tp)
+						initial++;
+					else
+						--up;
+				else
+					up++;
+			}
+			if(initial){
+				if(initial > q0)
+					initial = q0;
+				q0 -= initial;
+				textdelete(t, q0, q0+initial, tofile);
+			}
+			n = up-tp;
+			textinsert(t, q0, tp, n, tofile);
+			free(tp);
+			*nrp = n;
+			return q0;
+		}
+	goto Err;
+}
+
+void
+textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
+{
+	int c, i;
+	Text *u;
+
+	if(tofile && t->ncache != 0)
+		error("text.insert");
+	if(n == 0)
+		return;
+	if(tofile){
+		fileinsert(t->file, q0, r, n);
+		if(t->what == Body){
+			t->w->dirty = TRUE;
+			t->w->utflastqid = -1;
+		}
+		if(t->file->ntext > 1)
+			for(i=0; i<t->file->ntext; i++){
+				u = t->file->text[i];
+				if(u != t){
+					u->w->dirty = TRUE;	/* always a body */
+					textinsert(u, q0, r, n, FALSE);
+					textsetselect(u, u->q0, u->q1);
+					textscrdraw(u);
+				}
+			}
+					
+	}
+	if(q0 < t->q1)
+		t->q1 += n;
+	if(q0 < t->q0)
+		t->q0 += n;
+	if(q0 < t->org)
+		t->org += n;
+	else if(q0 <= t->org+t->fr.nchars)
+		frinsert(&t->fr, r, r+n, q0-t->org);
+	if(t->w){
+		c = 'i';
+		if(t->what == Body)
+			c = 'I';
+		if(n <= EVENTSIZE)
+			winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
+		else
+			winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
+	}
+}
+
+
+void
+textfill(Text *t)
+{
+	Rune *rp;
+	int i, n, m, nl;
+
+	if(t->fr.lastlinefull || t->nofill)
+		return;
+	if(t->ncache > 0){
+		if(t->w != nil)
+			wincommit(t->w, t);
+		else
+			textcommit(t, TRUE);
+	}
+	rp = fbufalloc();
+	do{
+		n = t->file->b.nc-(t->org+t->fr.nchars);
+		if(n == 0)
+			break;
+		if(n > 2000)	/* educated guess at reasonable amount */
+			n = 2000;
+		bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
+		/*
+		 * it's expensive to frinsert more than we need, so
+		 * count newlines.
+		 */
+		nl = t->fr.maxlines-t->fr.nlines;
+		m = 0;
+		for(i=0; i<n; ){
+			if(rp[i++] == '\n'){
+				m++;
+				if(m >= nl)
+					break;
+			}
+		}
+		frinsert(&t->fr, rp, rp+i, t->fr.nchars);
+	}while(t->fr.lastlinefull == FALSE);
+	fbuffree(rp);
+}
+
+void
+textdelete(Text *t, uint q0, uint q1, int tofile)
+{
+	uint n, p0, p1;
+	int i, c;
+	Text *u;
+
+	if(tofile && t->ncache != 0)
+		error("text.delete");
+	n = q1-q0;
+	if(n == 0)
+		return;
+	if(tofile){
+		filedelete(t->file, q0, q1);
+		if(t->what == Body){
+			t->w->dirty = TRUE;
+			t->w->utflastqid = -1;
+		}
+		if(t->file->ntext > 1)
+			for(i=0; i<t->file->ntext; i++){
+				u = t->file->text[i];
+				if(u != t){
+					u->w->dirty = TRUE;	/* always a body */
+					textdelete(u, q0, q1, FALSE);
+					textsetselect(u, u->q0, u->q1);
+					textscrdraw(u);
+				}
+			}
+	}
+	if(q0 < t->q0)
+		t->q0 -= min(n, t->q0-q0);
+	if(q0 < t->q1)
+		t->q1 -= min(n, t->q1-q0);
+	if(q1 <= t->org)
+		t->org -= n;
+	else if(q0 < t->org+t->fr.nchars){
+		p1 = q1 - t->org;
+		if(p1 > t->fr.nchars)
+			p1 = t->fr.nchars;
+		if(q0 < t->org){
+			t->org = q0;
+			p0 = 0;
+		}else
+			p0 = q0 - t->org;
+		frdelete(&t->fr, p0, p1);
+		textfill(t);
+	}
+	if(t->w){
+		c = 'd';
+		if(t->what == Body)
+			c = 'D';
+		winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
+	}
+}
+
+void
+textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
+{
+	*p0 = min(q0, t->file->b.nc);
+	*p1 = min(q1, t->file->b.nc);
+}
+
+Rune
+textreadc(Text *t, uint q)
+{
+	Rune r;
+
+	if(t->cq0<=q && q<t->cq0+t->ncache)
+		r = t->cache[q-t->cq0];
+	else
+		bufread(&t->file->b, q, &r, 1);
+	return r;
+}
+
+int
+textbswidth(Text *t, Rune c)
+{
+	uint q, eq;
+	Rune r;
+	int skipping;
+
+	/* there is known to be at least one character to erase */
+	if(c == 0x08)	/* ^H: erase character */
+		return 1;
+	q = t->q0;
+	skipping = TRUE;
+	while(q > 0){
+		r = textreadc(t, q-1);
+		if(r == '\n'){		/* eat at most one more character */
+			if(q == t->q0)	/* eat the newline */
+				--q;
+			break; 
+		}
+		if(c == 0x17){
+			eq = isalnum(r);
+			if(eq && skipping)	/* found one; stop skipping */
+				skipping = FALSE;
+			else if(!eq && !skipping)
+				break;
+		}
+		--q;
+	}
+	return t->q0-q;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+	uint q0, q1;
+	int nnb, nb, n, i;
+	Text *u;
+
+	if(t->what!=Body && r=='\n')
+		return;
+	switch(r){
+	case Kdown:
+	case Kleft:
+	case Kright:
+		n = t->fr.maxlines/2;
+		q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
+		textsetorigin(t, q0, FALSE);
+		return;
+	case Kup:
+		n = t->fr.maxlines/2;
+		q0 = textbacknl(t, t->org, n);
+		textsetorigin(t, q0, FALSE);
+		return;
+	}
+	if(t->what == Body){
+		seq++;
+		filemark(t->file);
+	}
+	if(t->q1 > t->q0){
+		if(t->ncache != 0)
+			error("text.type");
+		cut(t, t, nil, TRUE, TRUE, nil, 0);
+		t->eq0 = ~0;
+	}
+	textshow(t, t->q0, t->q0, 1);
+	switch(r){
+	case 0x1B:
+		if(t->eq0 != ~0)
+			textsetselect(t, t->eq0, t->q0);
+		if(t->ncache > 0){
+			if(t->w != nil)
+				wincommit(t->w, t);
+			else
+				textcommit(t, TRUE);
+		}
+		return;
+	case 0x08:	/* ^H: erase character */
+	case 0x15:	/* ^U: erase line */
+	case 0x17:	/* ^W: erase word */
+		if(t->q0 == 0)	/* nothing to erase */
+			return;
+		nnb = textbswidth(t, r);
+		q1 = t->q0;
+		q0 = q1-nnb;
+		/* if selection is at beginning of window, avoid deleting invisible text */
+		if(q0 < t->org){
+			q0 = t->org;
+			nnb = q1-q0;
+		}
+		if(nnb <= 0)
+			return;
+		for(i=0; i<t->file->ntext; i++){
+			u = t->file->text[i];
+			u->nofill = TRUE;
+			nb = nnb;
+			n = u->ncache;
+			if(n > 0){
+				if(q1 != u->cq0+n)
+					error("text.type backspace");
+				if(n > nb)
+					n = nb;
+				u->ncache -= n;
+				textdelete(u, q1-n, q1, FALSE);
+				nb -= n;
+			}
+			if(u->eq0==q1 || u->eq0==~0)
+				u->eq0 = q0;
+			if(nb && u==t)
+				textdelete(u, q0, q0+nb, TRUE);
+			if(u != t)
+				textsetselect(u, u->q0, u->q1);
+			else
+				textsetselect(t, q0, q0);
+			u->nofill = FALSE;
+		}
+		for(i=0; i<t->file->ntext; i++)
+			textfill(t->file->text[i]);
+		return;
+	}
+	/* otherwise ordinary character; just insert, typically in caches of all texts */
+	for(i=0; i<t->file->ntext; i++){
+		u = t->file->text[i];
+		if(u->eq0 == ~0)
+			u->eq0 = t->q0;
+		if(u->ncache == 0)
+			u->cq0 = t->q0;
+		else if(t->q0 != u->cq0+u->ncache)
+			error("text.type cq1");
+		textinsert(u, t->q0, &r, 1, FALSE);
+		if(u != t)
+			textsetselect(u, u->q0, u->q1);
+		if(u->ncache == u->ncachealloc){
+			u->ncachealloc += 10;
+			u->cache = runerealloc(u->cache, u->ncachealloc);
+		}
+		u->cache[u->ncache++] = r;
+	}
+	textsetselect(t, t->q0+1, t->q0+1);
+	if(r=='\n' && t->w!=nil)
+		wincommit(t->w, t);
+}
+
+void
+textcommit(Text *t, int tofile)
+{
+	if(t->ncache == 0)
+		return;
+	if(tofile)
+		fileinsert(t->file, t->cq0, t->cache, t->ncache);
+	if(t->what == Body){
+		t->w->dirty = TRUE;
+		t->w->utflastqid = -1;
+	}
+	t->ncache = 0;
+}
+
+static	Text	*clicktext;
+static	uint	clickmsec;
+static	Text	*selecttext;
+static	uint	selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+	if(f != &selecttext->fr)
+		error("frameselect not right frame");
+	textframescroll(selecttext, dl);
+}
+
+void
+textframescroll(Text *t, int dl)
+{
+	uint q0;
+
+	if(dl == 0){
+		scrsleep(100);
+		return;
+	}
+	if(dl < 0){
+		q0 = textbacknl(t, t->org, -dl);
+		if(selectq > t->org+t->fr.p0)
+			textsetselect(t, t->org+t->fr.p0, selectq);
+		else
+			textsetselect(t, selectq, t->org+t->fr.p0);
+	}else{
+		if(t->org+t->fr.nchars == t->file->b.nc)
+			return;
+		q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
+		if(selectq > t->org+t->fr.p1)
+			textsetselect(t, t->org+t->fr.p1, selectq);
+		else
+			textsetselect(t, selectq, t->org+t->fr.p1);
+	}
+	textsetorigin(t, q0, TRUE);
+}
+
+
+void
+textselect(Text *t)
+{
+	uint q0, q1;
+	int b, x, y;
+	int state;
+
+	selecttext = t;
+	/*
+	 * To have double-clicking and chording, we double-click
+	 * immediately if it might make sense.
+	 */
+	b = mouse->buttons;
+	q0 = t->q0;
+	q1 = t->q1;
+	selectq = t->org+frcharofpt(&t->fr, mouse->xy);
+	if(clicktext==t && mouse->msec-clickmsec<500)
+	if(q0==q1 && selectq==q0){
+		textdoubleclick(t, &q0, &q1);
+		textsetselect(t, q0, q1);
+		flushimage(display, 1);
+		x = mouse->xy.x;
+		y = mouse->xy.y;
+		/* stay here until something interesting happens */
+		do
+			readmouse(mousectl);
+		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
+		mouse->xy.x = x;	/* in case we're calling frselect */
+		mouse->xy.y = y;
+		q0 = t->q0;	/* may have changed */
+		q1 = t->q1;
+		selectq = q0;
+	}
+	if(mouse->buttons == b){
+		t->fr.scroll = framescroll;
+		frselect(&t->fr, mousectl);
+		/* horrible botch: while asleep, may have lost selection altogether */
+		if(selectq > t->file->b.nc)
+			selectq = t->org + t->fr.p0;
+		t->fr.scroll = nil;
+		if(selectq < t->org)
+			q0 = selectq;
+		else
+			q0 = t->org + t->fr.p0;
+		if(selectq > t->org+t->fr.nchars)
+			q1 = selectq;
+		else
+			q1 = t->org+t->fr.p1;
+	}
+	if(q0 == q1){
+		if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
+			textdoubleclick(t, &q0, &q1);
+			clicktext = nil;
+		}else{
+			clicktext = t;
+			clickmsec = mouse->msec;
+		}
+	}else
+		clicktext = nil;
+	textsetselect(t, q0, q1);
+	flushimage(display, 1);
+	state = 0;	/* undo when possible; +1 for cut, -1 for paste */
+	while(mouse->buttons){
+		mouse->msec = 0;
+		b = mouse->buttons;
+		if(b & 6){
+			if(state==0 && t->what==Body){
+				seq++;
+				filemark(t->w->body.file);
+			}
+			if(b & 2){
+				if(state==-1 && t->what==Body){
+					winundo(t->w, TRUE);
+					textsetselect(t, q0, t->q0);
+					state = 0;
+				}else if(state != 1){
+					cut(t, t, nil, TRUE, TRUE, nil, 0);
+					state = 1;
+				}
+			}else{
+				if(state==1 && t->what==Body){
+					winundo(t->w, TRUE);
+					textsetselect(t, q0, t->q1);
+					state = 0;
+				}else if(state != -1){
+					paste(t, t, nil, TRUE, FALSE, nil, 0);
+					state = -1;
+				}
+			}
+			textscrdraw(t);
+			clearmouse();
+		}
+		flushimage(display, 1);
+		while(mouse->buttons == b)
+			readmouse(mousectl);
+		clicktext = nil;
+	}
+}
+
+void
+textshow(Text *t, uint q0, uint q1, int doselect)
+{
+	int qe;
+	int nl;
+	uint q;
+
+	if(t->what != Body)
+		return;
+	if(t->w!=nil && t->fr.maxlines==0)
+		colgrow(t->col, t->w, 1);
+	if(doselect)
+		textsetselect(t, q0, q1);
+	qe = t->org+t->fr.nchars;
+	if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache)))
+		textscrdraw(t);
+	else{
+		if(t->w->nopen[QWevent] > 0)
+			nl = 3*t->fr.maxlines/4;
+		else
+			nl = t->fr.maxlines/4;
+		q = textbacknl(t, q0, nl);
+		/* avoid going backwards if trying to go forwards - long lines! */
+		if(!(q0>t->org && q<t->org))
+			textsetorigin(t, q, TRUE);
+		while(q0 > t->org+t->fr.nchars)
+			textsetorigin(t, t->org+1, FALSE);
+	}
+}
+
+static
+int
+region(int a, int b)
+{
+	if(a < b)
+		return -1;
+	if(a == b)
+		return 0;
+	return 1;
+}
+
+void
+selrestore(Frame *f, Point pt0, uint p0, uint p1)
+{
+	if(p1<=f->p0 || p0>=f->p1){
+		/* no overlap */
+		frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
+		return;
+	}
+	if(p0>=f->p0 && p1<=f->p1){
+		/* entirely inside */
+		frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+		return;
+	}
+
+	/* they now are known to overlap */
+
+	/* before selection */
+	if(p0 < f->p0){
+		frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
+		p0 = f->p0;
+		pt0 = frptofchar(f, p0);
+	}
+	/* after selection */
+	if(p1 > f->p1){
+		frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
+		p1 = f->p1;
+	}
+	/* inside selection */
+	frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+}
+
+void
+textsetselect(Text *t, uint q0, uint q1)
+{
+	int p0, p1;
+
+	/* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
+	t->q0 = q0;
+	t->q1 = q1;
+	/* compute desired p0,p1 from q0,q1 */
+	p0 = q0-t->org;
+	p1 = q1-t->org;
+	if(p0 < 0)
+		p0 = 0;
+	if(p1 < 0)
+		p1 = 0;
+	if(p0 > t->fr.nchars)
+		p0 = t->fr.nchars;
+	if(p1 > t->fr.nchars)
+		p1 = t->fr.nchars;
+	if(p0==t->fr.p0 && p1==t->fr.p1)
+		return;
+	/* screen disagrees with desired selection */
+	if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
+		/* no overlap or too easy to bother trying */
+		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
+		frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
+		goto Return;
+	}
+	/* overlap; avoid unnecessary painting */
+	if(p0 < t->fr.p0){
+		/* extend selection backwards */
+		frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
+	}else if(p0 > t->fr.p0){
+		/* trim first part of selection */
+		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
+	}
+	if(p1 > t->fr.p1){
+		/* extend selection forwards */
+		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
+	}else if(p1 < t->fr.p1){
+		/* trim last part of selection */
+		frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
+	}
+
+    Return:
+	t->fr.p0 = p0;
+	t->fr.p1 = p1;
+}
+
+/*
+ * Release the button in less than DELAY ms and it's considered a null selection
+ * if the mouse hardly moved, regardless of whether it crossed a char boundary.
+ */
+enum {
+	DELAY = 2,
+	MINMOVE = 4,
+};
+
+uint
+xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)	/* when called, button is down */
+{
+	uint p0, p1, q, tmp;
+	ulong msec;
+	Point mp, pt0, pt1, qt;
+	int reg, b;
+
+	mp = mc->m.xy;
+	b = mc->m.buttons;
+	msec = mc->m.msec;
+
+	/* remove tick */
+	if(f->p0 == f->p1)
+		frtick(f, frptofchar(f, f->p0), 0);
+	p0 = p1 = frcharofpt(f, mp);
+	pt0 = frptofchar(f, p0);
+	pt1 = frptofchar(f, p1);
+	reg = 0;
+	frtick(f, pt0, 1);
+	do{
+		q = frcharofpt(f, mc->m.xy);
+		if(p1 != q){
+			if(p0 == p1)
+				frtick(f, pt0, 0);
+			if(reg != region(q, p0)){	/* crossed starting point; reset */
+				if(reg > 0)
+					selrestore(f, pt0, p0, p1);
+				else if(reg < 0)
+					selrestore(f, pt1, p1, p0);
+				p1 = p0;
+				pt1 = pt0;
+				reg = region(q, p0);
+				if(reg == 0)
+					frdrawsel0(f, pt0, p0, p1, col, display->white);
+			}
+			qt = frptofchar(f, q);
+			if(reg > 0){
+				if(q > p1)
+					frdrawsel0(f, pt1, p1, q, col, display->white);
+
+				else if(q < p1)
+					selrestore(f, qt, q, p1);
+			}else if(reg < 0){
+				if(q > p1)
+					selrestore(f, pt1, p1, q);
+				else
+					frdrawsel0(f, qt, q, p1, col, display->white);
+			}
+			p1 = q;
+			pt1 = qt;
+		}
+		if(p0 == p1)
+			frtick(f, pt0, 1);
+		flushimage(f->display, 1);
+		readmouse(mc);
+	}while(mc->m.buttons == b);
+	if(mc->m.msec-msec < DELAY && p0!=p1
+	&& abs(mp.x-mc->m.xy.x)<MINMOVE
+	&& abs(mp.y-mc->m.xy.y)<MINMOVE) {
+		if(reg > 0)
+			selrestore(f, pt0, p0, p1);
+		else if(reg < 0)
+			selrestore(f, pt1, p1, p0);
+		p1 = p0;
+	}
+	if(p1 < p0){
+		tmp = p0;
+		p0 = p1;
+		p1 = tmp;
+	}
+	pt0 = frptofchar(f, p0);
+	if(p0 == p1)
+		frtick(f, pt0, 0);
+	selrestore(f, pt0, p0, p1);
+	/* restore tick */
+	if(f->p0 == f->p1)
+		frtick(f, frptofchar(f, f->p0), 1);
+	flushimage(f->display, 1);
+	*p1p = p1;
+	return p0;
+}
+
+int
+textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
+{
+	uint p0, p1;
+	int buts;
+	
+	p0 = xselect(&t->fr, mousectl, high, &p1);
+	buts = mousectl->m.buttons;
+	if((buts & mask) == 0){
+		*q0 = p0+t->org;
+		*q1 = p1+t->org;
+	}
+
+	while(mousectl->m.buttons)
+		readmouse(mousectl);
+	return buts;
+}
+
+int
+textselect2(Text *t, uint *q0, uint *q1, Text **tp)
+{
+	int buts;
+
+	*tp = nil;
+	buts = textselect23(t, q0, q1, but2col, 4);
+	if(buts & 4)
+		return 0;
+	if(buts & 1){	/* pick up argument */
+		*tp = argtext;
+		return 1;
+	}
+	return 1;
+}
+
+int
+textselect3(Text *t, uint *q0, uint *q1)
+{
+	int h;
+
+	h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
+	return h;
+}
+
+static Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
+static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
+static Rune left2[] =  { '\n', 0 };
+static Rune left3[] =  { '\'', '"', '`', 0 };
+
+static
+Rune *left[] = {
+	left1,
+	left2,
+	left3,
+	nil
+};
+static
+Rune *right[] = {
+	right1,
+	left2,
+	left3,
+	nil
+};
+
+void
+textdoubleclick(Text *t, uint *q0, uint *q1)
+{
+	int c, i;
+	Rune *r, *l, *p;
+	uint q;
+
+	for(i=0; left[i]!=nil; i++){
+		q = *q0;
+		l = left[i];
+		r = right[i];
+		/* try matching character to left, looking right */
+		if(q == 0)
+			c = '\n';
+		else
+			c = textreadc(t, q-1);
+		p = runestrchr(l, c);
+		if(p != nil){
+			if(textclickmatch(t, c, r[p-l], 1, &q))
+				*q1 = q-(c!='\n');
+			return;
+		}
+		/* try matching character to right, looking left */
+		if(q == t->file->b.nc)
+			c = '\n';
+		else
+			c = textreadc(t, q);
+		p = runestrchr(r, c);
+		if(p != nil){
+			if(textclickmatch(t, c, l[p-r], -1, &q)){
+				*q1 = *q0+(*q0<t->file->b.nc && c=='\n');
+				*q0 = q;
+				if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
+					(*q0)++;
+			}
+			return;
+		}
+	}
+	/* try filling out word to right */
+	while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
+		(*q1)++;
+	/* try filling out word to left */
+	while(*q0>0 && isalnum(textreadc(t, *q0-1)))
+		(*q0)--;
+}
+
+int
+textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
+{
+	Rune c;
+	int nest;
+
+	nest = 1;
+	for(;;){
+		if(dir > 0){
+			if(*q == t->file->b.nc)
+				break;
+			c = textreadc(t, *q);
+			(*q)++;
+		}else{
+			if(*q == 0)
+				break;
+			(*q)--;
+			c = textreadc(t, *q);
+		}
+		if(c == cr){
+			if(--nest==0)
+				return 1;
+		}else if(c == cl)
+			nest++;
+	}
+	return cl=='\n' && nest==1;
+}
+
+uint
+textbacknl(Text *t, uint p, uint n)
+{
+	int i, j;
+
+	/* look for start of this line if n==0 */
+	if(n==0 && p>0 && textreadc(t, p-1)!='\n')
+		n = 1;
+	i = n;
+	while(i-->0 && p>0){
+		--p;	/* it's at a newline now; back over it */
+		if(p == 0)
+			break;
+		/* at 128 chars, call it a line anyway */
+		for(j=128; --j>0 && p>0; p--)
+			if(textreadc(t, p-1)=='\n')
+				break;
+	}
+	return p;
+}
+
+void
+textsetorigin(Text *t, uint org, int exact)
+{
+	int i, a, fixup;
+	Rune *r;
+	uint n;
+
+	if(org>0 && !exact){
+		/* org is an estimate of the char posn; find a newline */
+		/* don't try harder than 256 chars */
+		for(i=0; i<256 && org<t->file->b.nc; i++){
+			if(textreadc(t, org) == '\n'){
+				org++;
+				break;
+			}
+			org++;
+		}
+	}
+	a = org-t->org;
+	fixup = 0;
+	if(a>=0 && a<t->fr.nchars){
+		frdelete(&t->fr, 0, a);
+		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+	}
+	else if(a<0 && -a<t->fr.nchars){
+		n = t->org - org;
+		r = runemalloc(n);
+		bufread(&t->file->b, org, r, n);
+		frinsert(&t->fr, r, r+n, 0);
+		free(r);
+	}else
+		frdelete(&t->fr, 0, t->fr.nchars);
+	t->org = org;
+	textfill(t);
+	textscrdraw(t);
+	textsetselect(t, t->q0, t->q1);
+	if(fixup && t->fr.p1 > t->fr.p0)
+		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
+}
+
+void
+textreset(Text *t)
+{
+	t->file->seq = 0;
+	t->eq0 = ~0;
+	/* do t->delete(0, t->nc, TRUE) without building backup stuff */
+	textsetselect(t, t->org, t->org);
+	frdelete(&t->fr, 0, t->fr.nchars);
+	t->org = 0;
+	t->q0 = 0;
+	t->q1 = 0;
+	filereset(t->file);
+	bufreset(&t->file->b);
+}
blob - /dev/null
blob + b281d68d71b894e75ee0296e51e8e5147f474f4d (mode 644)
--- /dev/null
+++ src/cmd/acme/time.c
@@ -0,0 +1,121 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static Channel*	ctimer;	/* chan(Timer*)[100] */
+static Timer *timer;
+
+static
+uint
+msec(void)
+{
+	return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+	t->next = timer;
+	timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+	t->cancel = TRUE;
+}
+
+static
+void
+timerproc(void *v)
+{
+	int i, nt, na, dt, del;
+	Timer **t, *x;
+	uint old, new;
+
+	USED(v);
+	threadsetname("timerproc");
+	rfork(RFFDG);
+	t = nil;
+	na = 0;
+	nt = 0;
+	old = msec();
+	for(;;){
+		sleep(1);	/* will sleep minimum incr */
+		new = msec();
+		dt = new-old;
+		old = new;
+		if(dt < 0)	/* timer wrapped; go around, losing a tick */
+			continue;
+		for(i=0; i<nt; i++){
+			x = t[i];
+			x->dt -= dt;
+			del = FALSE;
+			if(x->cancel){
+				timerstop(x);
+				del = TRUE;
+			}else if(x->dt <= 0){
+				/*
+				 * avoid possible deadlock if client is
+				 * now sending on ctimer
+				 */
+				if(nbsendul(x->c, 0) > 0)
+					del = TRUE;
+			}
+			if(del){
+				memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+				--nt;
+				--i;
+			}
+		}
+		if(nt == 0){
+			x = recvp(ctimer);
+	gotit:
+			if(nt == na){
+				na += 10;
+				t = realloc(t, na*sizeof(Timer*));
+				if(t == nil)
+					error("timer realloc failed");
+			}
+			t[nt++] = x;
+			old = msec();
+		}
+		if(nbrecv(ctimer, &x) > 0)
+			goto gotit;
+	}
+}
+
+void
+timerinit(void)
+{
+	ctimer = chancreate(sizeof(Timer*), 100);
+	proccreate(timerproc, nil, STACK);
+}
+
+Timer*
+timerstart(int dt)
+{
+	Timer *t;
+
+	t = timer;
+	if(t)
+		timer = timer->next;
+	else{
+		t = emalloc(sizeof(Timer));
+		t->c = chancreate(sizeof(int), 0);
+	}
+	t->next = nil;
+	t->dt = dt;
+	t->cancel = FALSE;
+	sendp(ctimer, t);
+	return t;
+}
blob - /dev/null
blob + 8e3cfa29e54a8aec81d8a8c3d5c7a26eba45ad48 (mode 644)
--- /dev/null
+++ src/cmd/acme/util.c
@@ -0,0 +1,395 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+static	Point		prevmouse;
+static	Window	*mousew;
+
+void
+cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
+{
+	uchar *q;
+	Rune *s;
+	int j, w;
+
+	/*
+	 * Always guaranteed that n bytes may be interpreted
+	 * without worrying about partial runes.  This may mean
+	 * reading up to UTFmax-1 more bytes than n; the caller
+	 * knows this.  If n is a firm limit, the caller should
+	 * set p[n] = 0.
+	 */
+	q = (uchar*)p;
+	s = r;
+	for(j=0; j<n; j+=w){
+		if(*q < Runeself){
+			w = 1;
+			*s = *q++;
+		}else{
+			w = chartorune(s, (char*)q);
+			q += w;
+		}
+		if(*s)
+			s++;
+		else if(nulls)
+			*nulls = TRUE;
+	}
+	*nb = (char*)q-p;
+	*nr = s-r;
+}
+
+void
+error(char *s)
+{
+	fprint(2, "acme: %s: %r\n", s);
+	abort();
+}
+
+Window*
+errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
+{
+	Window *w;
+	Rune *r;
+	int i, n;
+	static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
+
+	r = runemalloc(ndir+7);
+	if(n = ndir)	/* assign = */
+		runemove(r, dir, ndir);
+	runemove(r+n, Lpluserrors, 7);
+	n += 7;
+	w = lookfile(r, n);
+	if(w == nil){
+		if(row.ncol == 0)
+			if(rowadd(&row, nil, -1) == nil)
+				error("can't create column to make error window");
+		w = coladd(row.col[row.ncol-1], nil, nil, -1);
+		w->filemenu = FALSE;
+		winsetname(w, r, n);
+	}
+	free(r);
+	for(i=nincl; --i>=0; ){
+		n = runestrlen(incl[i]);
+		r = runemalloc(n);
+		runemove(r, incl[i], n);
+		winaddincl(w, r, n);
+	}
+	return w;
+}
+
+/* make new window, if necessary; return with it locked */
+Window*
+errorwin(Mntdir *md, int owner, Window *e)
+{
+	Window *w;
+
+	for(;;){
+		if(md == nil)
+			w = errorwin1(nil, 0, nil, 0);
+		else
+			w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
+		if(w != e)
+			winlock(w, owner);
+		if(w->col != nil)
+			break;
+		/* window was deleted too fast */
+		if(w != e)
+			winunlock(w);
+	}
+	return w;
+}
+
+static void
+printwarning(Window *ew, Mntdir *md, Rune *r)
+{
+	int nr, q0, owner;
+	Window *w;
+	Text *t;
+
+	if(r == nil)
+		error("runevsmprint failed");
+	nr = runestrlen(r);
+
+	if(row.ncol == 0){	/* really early error */
+		rowinit(&row, screen->clipr);
+		rowadd(&row, nil, -1);
+		rowadd(&row, nil, -1);
+		if(row.ncol == 0)
+			error("initializing columns in warning()");
+	}
+
+	w = errorwin(md, 'E', ew);
+	t = &w->body;
+	owner = w->owner;
+	if(owner == 0)
+		w->owner = 'E';
+	wincommit(w, t);
+	q0 = textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
+	textshow(t, q0, q0+nr, 1);
+	winsettag(t->w);
+	textscrdraw(t);
+	w->owner = owner;
+	w->dirty = FALSE;
+	if(ew != w)
+		winunlock(w);
+	free(r);
+}
+
+void
+warning(Mntdir *md, char *s, ...)
+{
+	Rune *r;
+	va_list arg;
+
+	va_start(arg, s);
+	r = runevsmprint(s, arg);
+	va_end(arg);
+	printwarning(nil, md, r);
+}
+
+/*
+ * Warningew is like warning but avoids locking the error window
+ * if it's already locked by checking that ew!=error window.
+ */
+void
+warningew(Window *ew, Mntdir *md, char *s, ...)
+{
+	Rune *r;
+	va_list arg;
+
+	va_start(arg, s);
+	r = runevsmprint(s, arg);
+	va_end(arg);
+	printwarning(ew, md, r);
+}
+
+int
+runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
+{
+	if(n1 != n2)
+		return FALSE;
+	return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
+}
+
+uint
+min(uint a, uint b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+uint
+max(uint a, uint b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+char*
+runetobyte(Rune *r, int n)
+{
+	char *s;
+
+	if(r == nil)
+		return nil;
+	s = emalloc(n*UTFmax+1);
+	setmalloctag(s, getcallerpc(&r));
+	snprint(s, n*UTFmax+1, "%.*S", n, r);
+	return s;
+}
+
+Rune*
+bytetorune(char *s, int *ip)
+{
+	Rune *r;
+	int nb, nr;
+
+	nb = strlen(s);
+	r = runemalloc(nb+1);
+	cvttorunes(s, nb, r, &nb, &nr, nil);
+	r[nr] = '\0';
+	*ip = nr;
+	return r;
+}
+
+int
+isalnum(Rune c)
+{
+	/*
+	 * Hard to get absolutely right.  Use what we know about ASCII
+	 * and assume anything above the Latin control characters is
+	 * potentially an alphanumeric.
+	 */
+	if(c <= ' ')
+		return FALSE;
+	if(0x7F<=c && c<=0xA0)
+		return FALSE;
+	if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+		return FALSE;
+	return TRUE;
+}
+
+int
+rgetc(void *v, uint n)
+{
+	return ((Rune*)v)[n];
+}
+
+int
+tgetc(void *a, uint n)
+{
+	Text *t;
+
+	t = a;
+	if(n >= t->file->b.nc)
+		return 0;
+	return textreadc(t, n);
+}
+
+Rune*
+skipbl(Rune *r, int n, int *np)
+{
+	while(n>0 && *r==' ' || *r=='\t' || *r=='\n'){
+		--n;
+		r++;
+	}
+	*np = n;
+	return r;
+}
+
+Rune*
+findbl(Rune *r, int n, int *np)
+{
+	while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
+		--n;
+		r++;
+	}
+	*np = n;
+	return r;
+}
+
+void
+savemouse(Window *w)
+{
+	prevmouse = mouse->xy;
+	mousew = w;
+}
+
+void
+restoremouse(Window *w)
+{
+	if(mousew!=nil && mousew==w)
+		moveto(mousectl, prevmouse);
+	mousew = nil;
+}
+
+void
+clearmouse()
+{
+	mousew = nil;
+}
+
+char*
+estrdup(char *s)
+{
+	char *t;
+
+	t = strdup(s);
+	if(t == nil)
+		error("strdup failed");
+	setmalloctag(t, getcallerpc(&s));
+	return t;
+}
+
+void*
+emalloc(uint n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil){
+		fprint(2, "allocating %d from %lux: %r\n", n, getcallerpc(&n));
+		*(int*)0=0;
+		error("malloc failed");
+	}
+	setmalloctag(p, getcallerpc(&n));
+	memset(p, 0, n);
+	return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+	p = realloc(p, n);
+	if(p == nil){
+		fprint(2, "reallocating %d: %r\n", n);
+		error("realloc failed");
+	}
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+/*
+ * Heuristic city.
+ */
+Window*
+makenewwindow(Text *t)
+{
+	Column *c;
+	Window *w, *bigw, *emptyw;
+	Text *emptyb;
+	int i, y, el;
+
+	if(activecol)
+		c = activecol;
+	else if(seltext && seltext->col)
+		c = seltext->col;
+	else if(t && t->col)
+		c = t->col;
+	else{
+		if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
+			error("can't make column");
+		c = row.col[row.ncol-1];
+	}
+	activecol = c;
+	if(t==nil || t->w==nil || c->nw==0)
+		return coladd(c, nil, nil, -1);
+
+	/* find biggest window and biggest blank spot */
+	emptyw = c->w[0];
+	bigw = emptyw;
+	for(i=1; i<c->nw; i++){
+		w = c->w[i];
+		/* use >= to choose one near bottom of screen */
+		if(w->body.fr.maxlines >= bigw->body.fr.maxlines)
+			bigw = w;
+		if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines)
+			emptyw = w;
+	}
+	emptyb = &emptyw->body;
+	el = emptyb->fr.maxlines-emptyb->fr.nlines;
+	/* if empty space is big, use it */
+	if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2))
+		y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height;
+	else{
+		/* if this window is in column and isn't much smaller, split it */
+		if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
+			bigw = t->w;
+		y = (bigw->r.min.y + bigw->r.max.y)/2;
+	}
+	w = coladd(c, nil, nil, y);
+	if(w->body.fr.maxlines < 2)
+		colgrow(w->col, w, 1);
+	return w;
+}
blob - /dev/null
blob + a9a1c4bf0cf1860be24294d9c3f89a6011a34f54 (mode 644)
--- /dev/null
+++ src/cmd/acme/wind.c
@@ -0,0 +1,576 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+int	winid;
+
+void
+wininit(Window *w, Window *clone, Rectangle r)
+{
+	Rectangle r1, br;
+	File *f;
+	Reffont *rf;
+	Rune *rp;
+	int nc;
+
+	w->tag.w = w;
+	w->body.w = w;
+	w->id = ++winid;
+	incref(&w->ref);
+	w->ctlfid = ~0;
+	w->utflastqid = -1;
+	r1 = r;
+	r1.max.y = r1.min.y + font->height;
+	incref(&reffont.ref);
+	f = fileaddtext(nil, &w->tag);
+	textinit(&w->tag, f, r1, &reffont, tagcols);
+	w->tag.what = Tag;
+	/* tag is a copy of the contents, not a tracked image */
+	if(clone){
+		textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE);
+		nc = clone->tag.file->b.nc;
+		rp = runemalloc(nc);
+		bufread(&clone->tag.file->b, 0, rp, nc);
+		textinsert(&w->tag, 0, rp, nc, TRUE);
+		free(rp);
+		filereset(w->tag.file);
+		textsetselect(&w->tag, nc, nc);
+	}
+	r1 = r;
+	r1.min.y += font->height + 1;
+	if(r1.max.y < r1.min.y)
+		r1.max.y = r1.min.y;
+	f = nil;
+	if(clone){
+		f = clone->body.file;
+		w->body.org = clone->body.org;
+		w->isscratch = clone->isscratch;
+		rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
+	}else
+		rf = rfget(FALSE, FALSE, FALSE, nil);
+	f = fileaddtext(f, &w->body);
+	w->body.what = Body;
+	textinit(&w->body, f, r1, rf, textcols);
+	r1.min.y -= 1;
+	r1.max.y = r1.min.y+1;
+	draw(screen, r1, tagcols[BORD], nil, ZP);
+	textscrdraw(&w->body);
+	w->r = r;
+	w->r.max.y = w->body.fr.r.max.y;
+	br.min = w->tag.scrollr.min;
+	br.max.x = br.min.x + Dx(button->r);
+	br.max.y = br.min.y + Dy(button->r);
+	draw(screen, br, button, nil, button->r.min);
+	w->filemenu = TRUE;
+	w->maxlines = w->body.fr.maxlines;
+	if(clone){
+		w->dirty = clone->dirty;
+		textsetselect(&w->body, clone->body.q0, clone->body.q1);
+		winsettag(w);
+	}
+}
+
+int
+winresize(Window *w, Rectangle r, int safe)
+{
+	Rectangle r1;
+	int y;
+	Image *b;
+	Rectangle br;
+
+	r1 = r;
+	r1.max.y = r1.min.y + font->height;
+	y = r1.max.y;
+	if(!safe || !eqrect(w->tag.fr.r, r1)){
+		y = textresize(&w->tag, r1);
+		b = button;
+		if(w->body.file->mod && !w->isdir && !w->isscratch)
+			b = modbutton;
+		br.min = w->tag.scrollr.min;
+		br.max.x = br.min.x + Dx(b->r);
+		br.max.y = br.min.y + Dy(b->r);
+		draw(screen, br, b, nil, b->r.min);
+	}
+	if(!safe || !eqrect(w->body.fr.r, r1)){
+		if(y+1+font->height > r.max.y){		/* no body */
+			r1.min.y = y;
+			r1.max.y = y;
+			textresize(&w->body, r1);
+			w->r = r;
+			w->r.max.y = y;
+			return y;
+		}
+		r1 = r;
+		r1.min.y = y;
+		r1.max.y = y + 1;
+		draw(screen, r1, tagcols[BORD], nil, ZP);
+		r1.min.y = y + 1;
+		r1.max.y = r.max.y;
+		y = textresize(&w->body, r1);
+		w->r = r;
+		w->r.max.y = y;
+		textscrdraw(&w->body);
+	}
+	w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
+	return w->r.max.y;
+}
+
+void
+winlock1(Window *w, int owner)
+{
+	incref(&w->ref);
+	qlock(&w->lk);
+	w->owner = owner;
+}
+
+void
+winlock(Window *w, int owner)
+{
+	int i;
+	File *f;
+
+	f = w->body.file;
+	for(i=0; i<f->ntext; i++)
+		winlock1(f->text[i]->w, owner);
+}
+
+void
+winunlock(Window *w)
+{
+	int i;
+	File *f;
+
+	f = w->body.file;
+	for(i=0; i<f->ntext; i++){
+		w = f->text[i]->w;
+		w->owner = 0;
+		qunlock(&w->lk);
+		winclose(w);
+		/* winclose() can change up f->text; beware */
+		if(f->ntext>0 && w != f->text[i]->w)
+			--i;	/* winclose() deleted window */
+	}
+}
+
+void
+winmousebut(Window *w)
+{
+	moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
+}
+
+void
+windirfree(Window *w)
+{
+	int i;
+	Dirlist *dl;
+
+	if(w->isdir){
+		for(i=0; i<w->ndl; i++){
+			dl = w->dlp[i];
+			free(dl->r);
+			free(dl);
+		}
+		free(w->dlp);
+	}
+	w->dlp = nil;
+	w->ndl = 0;
+}
+
+void
+winclose(Window *w)
+{
+	int i;
+
+	if(decref(&w->ref) == 0){
+		windirfree(w);
+		textclose(&w->tag);
+		textclose(&w->body);
+		if(activewin == w)
+			activewin = nil;
+		for(i=0; i<w->nincl; i++)
+			free(w->incl[i]);
+		free(w->incl);
+		free(w->events);
+		free(w);
+	}
+}
+
+void
+windelete(Window *w)
+{
+	Xfid *x;
+
+	x = w->eventx;
+	if(x){
+		w->nevents = 0;
+		free(w->events);
+		w->events = nil;
+		w->eventx = nil;
+		sendp(x->c, nil);	/* wake him up */
+	}
+}
+
+void
+winundo(Window *w, int isundo)
+{
+	Text *body;
+	int i;
+	File *f;
+	Window *v;
+
+	w->utflastqid = -1;
+	body = &w->body;
+	fileundo(body->file, isundo, &body->q0, &body->q1);
+	textshow(body, body->q0, body->q1, 1);
+	f = body->file;
+	for(i=0; i<f->ntext; i++){
+		v = f->text[i]->w;
+		v->dirty = (f->seq != v->putseq);
+		if(v != w){
+			v->body.q0 = v->body.fr.p0+v->body.org;
+			v->body.q1 = v->body.fr.p1+v->body.org;
+		}
+	}
+	winsettag(w);
+}
+
+void
+winsetname(Window *w, Rune *name, int n)
+{
+	Text *t;
+	Window *v;
+	int i;
+	static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 };
+	static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
+	t = &w->body;
+	if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
+		return;
+	w->isscratch = FALSE;
+	if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6))
+		w->isscratch = TRUE;
+	else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7))
+		w->isscratch = TRUE;
+	filesetname(t->file, name, n);
+	for(i=0; i<t->file->ntext; i++){
+		v = t->file->text[i]->w;
+		winsettag(v);
+		v->isscratch = w->isscratch;
+	}
+}
+
+void
+wintype(Window *w, Text *t, Rune r)
+{
+	int i;
+
+	texttype(t, r);
+	if(t->what == Body)
+		for(i=0; i<t->file->ntext; i++)
+			textscrdraw(t->file->text[i]);
+	winsettag(w);
+}
+
+void
+wincleartag(Window *w)
+{
+	int i, n;
+	Rune *r;
+
+	/* w must be committed */
+	n = w->tag.file->b.nc;
+	r = runemalloc(n);
+	bufread(&w->tag.file->b, 0, r, n);
+	for(i=0; i<n; i++)
+		if(r[i]==' ' || r[i]=='\t')
+			break;
+	for(; i<n; i++)
+		if(r[i] == '|')
+			break;
+	if(i == n)
+		return;
+	i++;
+	textdelete(&w->tag, i, n, TRUE);
+	free(r);
+	w->tag.file->mod = FALSE;
+	if(w->tag.q0 > i)
+		w->tag.q0 = i;
+	if(w->tag.q1 > i)
+		w->tag.q1 = i;
+	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
+}
+
+void
+winsettag1(Window *w)
+{
+	int i, j, k, n, bar, dirty;
+	Rune *new, *old, *r;
+	Image *b;
+	uint q0, q1;
+	Rectangle br;
+	static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ',
+		'S', 'n', 'a', 'r', 'f', 0 };
+	static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 };
+	static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 };
+	static Rune Lget[] = { ' ', 'G', 'e', 't', 0 };
+	static Rune Lput[] = { ' ', 'P', 'u', 't', 0 };
+	static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 };
+	static Rune Lpipe[] = { ' ', '|', 0 };
+	/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
+	if(w->tag.ncache!=0 || w->tag.file->mod)
+		wincommit(w, &w->tag);	/* check file name; also guarantees we can modify tag contents */
+	old = runemalloc(w->tag.file->b.nc+1);
+	bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
+	old[w->tag.file->b.nc] = '\0';
+	for(i=0; i<w->tag.file->b.nc; i++)
+		if(old[i]==' ' || old[i]=='\t')
+			break;
+	if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
+		textdelete(&w->tag, 0, i, TRUE);
+		textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
+		free(old);
+		old = runemalloc(w->tag.file->b.nc+1);
+		bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
+		old[w->tag.file->b.nc] = '\0';
+	}
+	new = runemalloc(w->body.file->nname+100);
+	i = 0;
+	runemove(new+i, w->body.file->name, w->body.file->nname);
+	i += w->body.file->nname;
+	runemove(new+i, Ldelsnarf, 10);
+	i += 10;
+	if(w->filemenu){
+		if(w->body.file->delta.nc>0 || w->body.ncache){
+			runemove(new+i, Lundo, 5);
+			i += 5;
+		}
+		if(w->body.file->epsilon.nc > 0){
+			runemove(new+i, Lredo, 5);
+			i += 5;
+		}
+		dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
+		if(!w->isdir && dirty){
+			runemove(new+i, Lput, 4);
+			i += 4;
+		}
+	}
+	if(w->isdir){
+		runemove(new+i, Lget, 4);
+		i += 4;
+	}
+	runemove(new+i, Lpipe, 2);
+	i += 2;
+	r = runestrchr(old, '|');
+	if(r)
+		k = r-old+1;
+	else{
+		k = w->tag.file->b.nc;
+		if(w->body.file->seq == 0){
+			runemove(new+i, Llook, 6);
+			i += 6;
+		}
+	}
+	new[i] = 0;
+	if(runestrlen(new) != i)
+		fprint(2, "s '%S' len not %d\n", new, i);
+	assert(i==runestrlen(new));
+	if(runeeq(new, i, old, k) == FALSE){
+		n = k;
+		if(n > i)
+			n = i;
+		for(j=0; j<n; j++)
+			if(old[j] != new[j])
+				break;
+		q0 = w->tag.q0;
+		q1 = w->tag.q1;
+		textdelete(&w->tag, j, k, TRUE);
+		textinsert(&w->tag, j, new+j, i-j, TRUE);
+		/* try to preserve user selection */
+		r = runestrchr(old, '|');
+		if(r){
+			bar = r-old;
+			if(q0 > bar){
+				bar = (runestrchr(new, '|')-new)-bar;
+				w->tag.q0 = q0+bar;
+				w->tag.q1 = q1+bar;
+			}
+		}
+	}
+	free(old);
+	free(new);
+	w->tag.file->mod = FALSE;
+	n = w->tag.file->b.nc+w->tag.ncache;
+	if(w->tag.q0 > n)
+		w->tag.q0 = n;
+	if(w->tag.q1 > n)
+		w->tag.q1 = n;
+	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
+	b = button;
+	if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
+		b = modbutton;
+	br.min = w->tag.scrollr.min;
+	br.max.x = br.min.x + Dx(b->r);
+	br.max.y = br.min.y + Dy(b->r);
+	draw(screen, br, b, nil, b->r.min);
+}
+
+void
+winsettag(Window *w)
+{
+	int i;
+	File *f;
+	Window *v;
+
+	f = w->body.file;
+	for(i=0; i<f->ntext; i++){
+		v = f->text[i]->w;
+		if(v->col->safe || v->body.fr.maxlines>0)
+			winsettag1(v);
+	}
+}
+
+void
+wincommit(Window *w, Text *t)
+{
+	Rune *r;
+	int i;
+	File *f;
+
+	textcommit(t, TRUE);
+	f = t->file;
+	if(f->ntext > 1)
+		for(i=0; i<f->ntext; i++)
+			textcommit(f->text[i], FALSE);	/* no-op for t */
+	if(t->what == Body)
+		return;
+	r = runemalloc(w->tag.file->b.nc);
+	bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc);
+	for(i=0; i<w->tag.file->b.nc; i++)
+		if(r[i]==' ' || r[i]=='\t')
+			break;
+	if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
+		seq++;
+		filemark(w->body.file);
+		w->body.file->mod = TRUE;
+		w->dirty = TRUE;
+		winsetname(w, r, i);
+		winsettag(w);
+	}
+	free(r);
+}
+
+void
+winaddincl(Window *w, Rune *r, int n)
+{
+	char *a;
+	Dir *d;
+	Runestr rs;
+
+	a = runetobyte(r, n);
+	d = dirstat(a);
+	if(d == nil){
+		if(a[0] == '/')
+			goto Rescue;
+		rs = dirname(&w->body, r, n);
+		r = rs.r;
+		n = rs.nr;
+		free(a);
+		a = runetobyte(r, n);
+		d = dirstat(a);
+		if(d == nil)
+			goto Rescue;
+		r = runerealloc(r, n+1);
+		r[n] = 0;
+	}
+	free(a);
+	if((d->qid.type&QTDIR) == 0){
+		free(d);
+		warning(nil, "%s: not a directory\n", a);
+		free(r);
+		return;
+	}
+	free(d);
+	w->nincl++;
+	w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
+	memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
+	w->incl[0] = runemalloc(n+1);
+	runemove(w->incl[0], r, n);
+	free(r);
+	return;
+
+Rescue:
+	warning(nil, "%s: %r\n", a);
+	free(r);
+	free(a);
+	return;
+}
+
+int
+winclean(Window *w, int conservative)	/* as it stands, conservative is always TRUE */
+{
+	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
+		return TRUE;
+	if(!conservative && w->nopen[QWevent]>0)
+		return TRUE;
+	if(w->dirty){
+		if(w->body.file->nname)
+			warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
+		else{
+			if(w->body.file->b.nc < 100)	/* don't whine if it's too small */
+				return TRUE;
+			warning(nil, "unnamed file modified\n");
+		}
+		w->dirty = FALSE;
+		return FALSE;
+	}
+	return TRUE;
+}
+
+void
+winctlprint(Window *w, char *buf, int fonts)
+{
+	int n;
+
+	n = sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc,
+		w->body.file->b.nc, w->isdir, w->dirty);
+	if(fonts)
+		sprint(buf+n, "%11d %s" , Dx(w->body.fr.r), w->body.reffont->f->name);
+}
+
+void
+winevent(Window *w, char *fmt, ...)
+{
+	int n;
+	char *b;
+	Xfid *x;
+	va_list arg;
+
+	if(w->nopen[QWevent] == 0)
+		return;
+	if(w->owner == 0)
+		error("no window owner");
+	va_start(arg, fmt);
+	b = vsmprint(fmt, arg);
+	va_end(arg);
+	if(b == nil)
+		error("vsmprint failed");
+	n = strlen(b);
+	w->events = realloc(w->events, w->nevents+1+n);
+	w->events[w->nevents++] = w->owner;
+	memmove(w->events+w->nevents, b, n);
+	free(b);
+	w->nevents += n;
+	x = w->eventx;
+	if(x){
+		w->eventx = nil;
+		sendp(x->c, nil);
+	}
+}
blob - /dev/null
blob + f397623ed4bdb89e0f9f53aad5bfcaf41a9eb222 (mode 644)
--- /dev/null
+++ src/cmd/acme/xfid.c
@@ -0,0 +1,1046 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+	Ctlsize	= 5*12
+};
+
+char	Edel[]		= "deleted window";
+char	Ebadctl[]		= "ill-formed control message";
+char	Ebadaddr[]	= "bad address syntax";
+char	Eaddr[]		= "address out of range";
+char	Einuse[]		= "already in use";
+char	Ebadevent[]	= "bad event syntax";
+extern char Eperm[];
+
+static
+void
+clampaddr(Window *w)
+{
+	if(w->addr.q0 < 0)
+		w->addr.q0 = 0;
+	if(w->addr.q1 < 0)
+		w->addr.q1 = 0;
+	if(w->addr.q0 > w->body.file->b.nc)
+		w->addr.q0 = w->body.file->b.nc;
+	if(w->addr.q1 > w->body.file->b.nc)
+		w->addr.q1 = w->body.file->b.nc;
+}
+
+void
+xfidctl(void *arg)
+{
+	Xfid *x;
+	void (*f)(Xfid*);
+
+	threadsetname("xfidctlthread");
+	x = arg;
+	for(;;){
+		f = recvp(x->c);
+		(*f)(x);
+		flushimage(display, 1);
+		sendp(cxfidfree, x);
+	}
+}
+
+void
+xfidflush(Xfid *x)
+{
+	Fcall fc;
+	int i, j;
+	Window *w;
+	Column *c;
+	Xfid *wx;
+
+	/* search windows for matching tag */
+	qlock(&row.lk);
+	for(j=0; j<row.ncol; j++){
+		c = row.col[j];
+		for(i=0; i<c->nw; i++){
+			w = c->w[i];
+			winlock(w, 'E');
+			wx = w->eventx;
+			if(wx!=nil && wx->fcall.tag==x->fcall.oldtag){
+				w->eventx = nil;
+				wx->flushed = TRUE;
+				sendp(wx->c, nil);
+				winunlock(w);
+				goto out;
+			}
+			winunlock(w);
+		}
+	}
+out:
+	qunlock(&row.lk);
+	respond(x, &fc, nil);
+}
+
+void
+xfidopen(Xfid *x)
+{
+	Fcall fc;
+	Window *w;
+	Text *t;
+	char *s;
+	Rune *r;
+	int m, n, q, q0, q1;
+
+	w = x->f->w;
+	t = &w->body;
+	if(w){
+		winlock(w, 'E');
+		q = FILE(x->f->qid);
+		switch(q){
+		case QWaddr:
+			if(w->nopen[q]++ == 0){
+				w->addr = (Range){0,0};
+				w->limit = (Range){-1,-1};
+			}
+			break;
+		case QWdata:
+			w->nopen[q]++;
+			break;
+		case QWevent:
+			if(w->nopen[q]++ == 0){
+				if(!w->isdir && w->col!=nil){
+					w->filemenu = FALSE;
+					winsettag(w);
+				}
+			}
+			break;
+		case QWrdsel:
+			/*
+			 * Use a temporary file.
+			 * A pipe would be the obvious, but we can't afford the
+			 * broken pipe notification.  Using the code to read QWbody
+			 * is n², which should probably also be fixed.  Even then,
+			 * though, we'd need to squirrel away the data in case it's
+			 * modified during the operation, e.g. by |sort
+			 */
+			if(w->rdselfd > 0){
+				winunlock(w);
+				respond(x, &fc, Einuse);
+				return;
+			}
+			w->rdselfd = tempfile();
+			if(w->rdselfd < 0){
+				winunlock(w);
+				respond(x, &fc, "can't create temp file");
+				return;
+			}
+			w->nopen[q]++;
+			q0 = t->q0;
+			q1 = t->q1;
+			r = fbufalloc();
+			s = fbufalloc();
+			while(q0 < q1){
+				n = q1 - q0;
+				if(n > BUFSIZE/UTFmax)
+					n = BUFSIZE/UTFmax;
+				bufread(&t->file->b, q0, r, n);
+				m = snprint(s, BUFSIZE+1, "%.*S", n, r);
+				if(write(w->rdselfd, s, m) != m){
+					warning(nil, "can't write temp file for pipe command %r\n");
+					break;
+				}
+				q0 += n;
+			}
+			fbuffree(s);
+			fbuffree(r);
+			break;
+		case QWwrsel:
+			w->nopen[q]++;
+			seq++;
+			filemark(t->file);
+			cut(t, t, nil, FALSE, TRUE, nil, 0);
+			w->wrselrange = (Range){t->q1, t->q1};
+			w->nomark = TRUE;
+			break;
+		case QWeditout:
+			if(editing == FALSE){
+				winunlock(w);
+				respond(x, &fc, Eperm);
+				return;
+			}
+			w->wrselrange = (Range){t->q1, t->q1};
+			break;
+		}
+		winunlock(w);
+	}
+	fc.qid = x->f->qid;
+	fc.iounit = messagesize-IOHDRSZ;
+	x->f->open = TRUE;
+	respond(x, &fc, nil);
+}
+
+void
+xfidclose(Xfid *x)
+{
+	Fcall fc;
+	Window *w;
+	int q;
+	Text *t;
+
+	w = x->f->w;
+	x->f->busy = FALSE;
+	if(x->f->open == FALSE){
+		if(w != nil)
+			winclose(w);
+		respond(x, &fc, nil);
+		return;
+	}
+
+	x->f->open = FALSE;
+	if(w){
+		winlock(w, 'E');
+		q = FILE(x->f->qid);
+		switch(q){
+		case QWctl:
+			if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
+				w->ctlfid = ~0;
+				qunlock(&w->ctllock);
+			}
+			break;
+		case QWdata:
+			w->nomark = FALSE;
+			/* fall through */
+		case QWaddr:
+		case QWevent:	/* BUG: do we need to shut down Xfid? */
+			if(--w->nopen[q] == 0){
+				if(q == QWdata)
+					w->nomark = FALSE;
+				if(q==QWevent && !w->isdir && w->col!=nil){
+					w->filemenu = TRUE;
+					winsettag(w);
+				}
+				if(q == QWevent){
+					free(w->dumpstr);
+					free(w->dumpdir);
+					w->dumpstr = nil;
+					w->dumpdir = nil;
+				}
+			}
+			break;
+		case QWrdsel:
+			close(w->rdselfd);
+			w->rdselfd = 0;
+			break;
+		case QWwrsel:
+			w->nomark = FALSE;
+			t = &w->body;
+			/* before: only did this if !w->noscroll, but that didn't seem right in practice */
+			textshow(t, min(w->wrselrange.q0, t->file->b.nc),
+				min(w->wrselrange.q1, t->file->b.nc), 1);
+			textscrdraw(t);
+			break;
+		}
+		winunlock(w);
+		winclose(w);
+	}
+	respond(x, &fc, nil);
+}
+
+void
+xfidread(Xfid *x)
+{
+	Fcall fc;
+	int n, q;
+	uint off;
+	char *b;
+	char buf[128];
+	Window *w;
+
+	q = FILE(x->f->qid);
+	w = x->f->w;
+	if(w == nil){
+		fc.count = 0;
+		switch(q){
+		case Qcons:
+		case Qlabel:
+			break;
+		case Qindex:
+			xfidindexread(x);
+			return;
+		default:
+			warning(nil, "unknown qid %d\n", q);
+			break;
+		}
+		respond(x, &fc, nil);
+		return;
+	}
+	winlock(w, 'F');
+	if(w->col == nil){
+		winunlock(w);
+		respond(x, &fc, Edel);
+		return;
+	}
+	off = x->fcall.offset;
+	switch(q){
+	case QWaddr:
+		textcommit(&w->body, TRUE);
+		clampaddr(w);
+		sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1);
+		goto Readbuf;
+
+	case QWbody:
+		xfidutfread(x, &w->body, w->body.file->b.nc, QWbody);
+		break;
+
+	case QWctl:
+		winctlprint(w, buf, 1);
+		goto Readbuf;
+
+	Readbuf:
+		n = strlen(buf);
+		if(off > n)
+			off = n;
+		if(off+x->fcall.count > n)
+			x->fcall.count = n-off;
+		fc.count = x->fcall.count;
+		fc.data = buf+off;
+		respond(x, &fc, nil);
+		break;
+
+	case QWevent:
+		xfideventread(x, w);
+		break;
+
+	case QWdata:
+		/* BUG: what should happen if q1 > q0? */
+		if(w->addr.q0 > w->body.file->b.nc){
+			respond(x, &fc, Eaddr);
+			break;
+		}
+		w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc);
+		w->addr.q1 = w->addr.q0;
+		break;
+
+	case QWtag:
+		xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag);
+		break;
+
+	case QWrdsel:
+		seek(w->rdselfd, off, 0);
+		n = x->fcall.count;
+		if(n > BUFSIZE)
+			n = BUFSIZE;
+		b = fbufalloc();
+		n = read(w->rdselfd, b, n);
+		if(n < 0){
+			respond(x, &fc, "I/O error in temp file");
+			break;
+		}
+		fc.count = n;
+		fc.data = b;
+		respond(x, &fc, nil);
+		fbuffree(b);
+		break;
+
+	default:
+		sprint(buf, "unknown qid %d in read", q);
+		respond(x, &fc, nil);
+	}
+	winunlock(w);
+}
+
+void
+xfidwrite(Xfid *x)
+{
+	Fcall fc;
+	int c, cnt, qid, q, nb, nr, eval;
+	char buf[64], *err;
+	Window *w;
+	Rune *r;
+	Range a;
+	Text *t;
+	uint q0, tq0, tq1;
+
+	qid = FILE(x->f->qid);
+	w = x->f->w;
+	if(w){
+		c = 'F';
+		if(qid==QWtag || qid==QWbody)
+			c = 'E';
+		winlock(w, c);
+		if(w->col == nil){
+			winunlock(w);
+			respond(x, &fc, Edel);
+			return;
+		}
+	}
+	x->fcall.data[x->fcall.count] = 0;
+	switch(qid){
+	case Qcons:
+		w = errorwin(x->f->mntdir, 'X', nil);
+		t=&w->body;
+		goto BodyTag;
+
+	case Qlabel:
+		fc.count = x->fcall.count;
+		respond(x, &fc, nil);
+		break;
+
+	case QWaddr:
+		x->fcall.data[x->fcall.count] = 0;
+		r = bytetorune(x->fcall.data, &nr);
+		t = &w->body;
+		wincommit(w, t);
+		eval = TRUE;
+		a = address(x->f->mntdir, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
+		free(r);
+		if(nb < nr){
+			respond(x, &fc, Ebadaddr);
+			break;
+		}
+		if(!eval){
+			respond(x, &fc, Eaddr);
+			break;
+		}
+		w->addr = a;
+		fc.count = x->fcall.count;
+		respond(x, &fc, nil);
+		break;
+
+	case Qeditout:
+	case QWeditout:
+		r = bytetorune(x->fcall.data, &nr);
+		if(w)
+			err = edittext(w, w->wrselrange.q1, r, nr);
+		else
+			err = edittext(nil, 0, r, nr);
+		free(r);
+		if(err != nil){
+			respond(x, &fc, err);
+			break;
+		}
+		fc.count = x->fcall.count;
+		respond(x, &fc, nil);
+		break;
+
+	case QWbody:
+	case QWwrsel:
+		t = &w->body;
+		goto BodyTag;
+
+	case QWctl:
+		xfidctlwrite(x, w);
+		break;
+
+	case QWdata:
+		a = w->addr;
+		t = &w->body;
+		wincommit(w, t);
+		if(a.q0>t->file->b.nc || a.q1>t->file->b.nc){
+			respond(x, &fc, Eaddr);
+			break;
+		}
+		r = runemalloc(x->fcall.count);
+		cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil);
+		if(w->nomark == FALSE){
+			seq++;
+			filemark(t->file);
+		}
+		q0 = a.q0;
+		if(a.q1 > q0){
+			textdelete(t, q0, a.q1, TRUE);
+			w->addr.q1 = q0;
+		}
+		tq0 = t->q0;
+		tq1 = t->q1;
+		textinsert(t, q0, r, nr, TRUE);
+		if(tq0 >= q0)
+			tq0 += nr;
+		if(tq1 >= q0)
+			tq1 += nr;
+		textsetselect(t, tq0, tq1);
+		if(!t->w->noscroll)
+			textshow(t, q0, q0+nr, 0);
+		textscrdraw(t);
+		winsettag(w);
+		free(r);
+		w->addr.q0 += nr;
+		w->addr.q1 = w->addr.q0;
+		fc.count = x->fcall.count;
+		respond(x, &fc, nil);
+		break;
+
+	case QWevent:
+		xfideventwrite(x, w);
+		break;
+
+	case QWtag:
+		t = &w->tag;
+		goto BodyTag;
+
+	BodyTag:
+		q = x->f->nrpart;
+		cnt = x->fcall.count;
+		if(q > 0){
+			memmove(x->fcall.data+q, x->fcall.data, cnt);	/* there's room; see fsysproc */
+			memmove(x->fcall.data, x->f->rpart, q);
+			cnt += q;
+			x->f->nrpart = 0;
+		}
+		r = runemalloc(cnt);
+		cvttorunes(x->fcall.data, cnt-UTFmax, r, &nb, &nr, nil);
+		/* approach end of buffer */
+		while(fullrune(x->fcall.data+nb, cnt-nb)){
+			c = nb;
+			nb += chartorune(&r[nr], x->fcall.data+c);
+			if(r[nr])
+				nr++;
+		}
+		if(nb < cnt){
+			memmove(x->f->rpart, x->fcall.data+nb, cnt-nb);
+			x->f->nrpart = cnt-nb;
+		}
+		if(nr > 0){
+			wincommit(w, t);
+			if(qid == QWwrsel){
+				q0 = w->wrselrange.q1;
+				if(q0 > t->file->b.nc)
+					q0 = t->file->b.nc;
+			}else
+				q0 = t->file->b.nc;
+			if(qid == QWtag)
+				textinsert(t, q0, r, nr, TRUE);
+			else{
+				if(w->nomark == FALSE){
+					seq++;
+					filemark(t->file);
+				}
+				q0 = textbsinsert(t, q0, r, nr, TRUE, &nr);
+				textsetselect(t, t->q0, t->q1);	/* insert could leave it somewhere else */
+				if(qid!=QWwrsel && !t->w->noscroll)
+					textshow(t, q0+nr, q0+nr, 1);
+				textscrdraw(t);
+			}
+			winsettag(w);
+			if(qid == QWwrsel)
+				w->wrselrange.q1 += nr;
+			free(r);
+		}
+		fc.count = x->fcall.count;
+		respond(x, &fc, nil);
+		break;
+
+	default:
+		sprint(buf, "unknown qid %d in write", qid);
+		respond(x, &fc, buf);
+		break;
+	}
+	if(w)
+		winunlock(w);
+}
+
+void
+xfidctlwrite(Xfid *x, Window *w)
+{
+	Fcall fc;
+	int i, m, n, nb, nr, nulls;
+	Rune *r;
+	char *err, *p, *pp, *q, *e;
+	int isfbuf, scrdraw, settag;
+	Text *t;
+
+	err = nil;
+	e = x->fcall.data+x->fcall.count;
+	scrdraw = FALSE;
+	settag = FALSE;
+	isfbuf = TRUE;
+	if(x->fcall.count < RBUFSIZE)
+		r = fbufalloc();
+	else{
+		isfbuf = FALSE;
+		r = emalloc(x->fcall.count*UTFmax+1);
+	}
+	x->fcall.data[x->fcall.count] = 0;
+	textcommit(&w->tag, TRUE);
+	for(n=0; n<x->fcall.count; n+=m){
+		p = x->fcall.data+n;
+		if(strncmp(p, "lock", 4) == 0){	/* make window exclusive use */
+			qlock(&w->ctllock);
+			w->ctlfid = x->f->fid;
+			m = 4;
+		}else
+		if(strncmp(p, "unlock", 6) == 0){	/* release exclusive use */
+			w->ctlfid = ~0;
+			qunlock(&w->ctllock);
+			m = 6;
+		}else
+		if(strncmp(p, "clean", 5) == 0){	/* mark window 'clean', seq=0 */
+			t = &w->body;
+			t->eq0 = ~0;
+			filereset(t->file);
+			t->file->mod = FALSE;
+			w->dirty = FALSE;
+			settag = TRUE;
+			m = 5;
+		}else
+		if(strncmp(p, "dirty", 5) == 0){	/* mark window 'dirty' */
+			t = &w->body;
+			/* doesn't change sequence number, so "Put" won't appear.  it shouldn't. */
+			t->file->mod = TRUE;
+			w->dirty = TRUE;
+			settag = TRUE;
+			m = 5;
+		}else
+		if(strncmp(p, "show", 4) == 0){	/* show dot */
+			t = &w->body;
+			textshow(t, t->q0, t->q1, 1);
+			m = 4;
+		}else
+		if(strncmp(p, "name ", 5) == 0){	/* set file name */
+			pp = p+5;
+			m = 5;
+			q = memchr(pp, '\n', e-pp);
+			if(q==nil || q==pp){
+				err = Ebadctl;
+				break;
+			}
+			*q = 0;
+			nulls = FALSE;
+			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+			if(nulls){
+				err = "nulls in file name";
+				break;
+			}
+			for(i=0; i<nr; i++)
+				if(r[i] <= ' '){
+					err = "bad character in file name";
+					goto out;
+				}
+out:
+			seq++;
+			filemark(w->body.file);
+			winsetname(w, r, nr);
+			m += (q+1) - pp;
+		}else
+		if(strncmp(p, "dump ", 5) == 0){	/* set dump string */
+			pp = p+5;
+			m = 5;
+			q = memchr(pp, '\n', e-pp);
+			if(q==nil || q==pp){
+				err = Ebadctl;
+				break;
+			}
+			*q = 0;
+			nulls = FALSE;
+			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+			if(nulls){
+				err = "nulls in dump string";
+				break;
+			}
+			w->dumpstr = runetobyte(r, nr);
+			m += (q+1) - pp;
+		}else
+		if(strncmp(p, "dumpdir ", 8) == 0){	/* set dump directory */
+			pp = p+8;
+			m = 8;
+			q = memchr(pp, '\n', e-pp);
+			if(q==nil || q==pp){
+				err = Ebadctl;
+				break;
+			}
+			*q = 0;
+			nulls = FALSE;
+			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
+			if(nulls){
+				err = "nulls in dump directory string";
+				break;
+			}
+			w->dumpdir = runetobyte(r, nr);
+			m += (q+1) - pp;
+		}else
+		if(strncmp(p, "delete", 6) == 0){	/* delete for sure */
+			colclose(w->col, w, TRUE);
+			m = 6;
+		}else
+		if(strncmp(p, "del", 3) == 0){	/* delete, but check dirty */
+			if(!winclean(w, TRUE)){
+				err = "file dirty";
+				break;
+			}
+			colclose(w->col, w, TRUE);
+			m = 3;
+		}else
+		if(strncmp(p, "get", 3) == 0){	/* get file */
+			get(&w->body, nil, nil, FALSE, XXX, nil, 0);
+			m = 3;
+		}else
+		if(strncmp(p, "put", 3) == 0){	/* put file */
+			put(&w->body, nil, nil, XXX, XXX, nil, 0);
+			m = 3;
+		}else
+		if(strncmp(p, "dot=addr", 8) == 0){	/* set dot */
+			textcommit(&w->body, TRUE);
+			clampaddr(w);
+			w->body.q0 = w->addr.q0;
+			w->body.q1 = w->addr.q1;
+			textsetselect(&w->body, w->body.q0, w->body.q1);
+			settag = TRUE;
+			m = 8;
+		}else
+		if(strncmp(p, "addr=dot", 8) == 0){	/* set addr */
+			w->addr.q0 = w->body.q0;
+			w->addr.q1 = w->body.q1;
+			m = 8;
+		}else
+		if(strncmp(p, "limit=addr", 10) == 0){	/* set limit */
+			textcommit(&w->body, TRUE);
+			clampaddr(w);
+			w->limit.q0 = w->addr.q0;
+			w->limit.q1 = w->addr.q1;
+			m = 10;
+		}else
+		if(strncmp(p, "nomark", 6) == 0){	/* turn off automatic marking */
+			w->nomark = TRUE;
+			m = 6;
+		}else
+		if(strncmp(p, "mark", 4) == 0){	/* mark file */
+			seq++;
+			filemark(w->body.file);
+			settag = TRUE;
+			m = 4;
+		}else
+		if(strncmp(p, "noscroll", 8) == 0){	/* turn off automatic scrolling */
+			w->noscroll = TRUE;
+			m = 8;
+		}else
+		if(strncmp(p, "cleartag", 8) == 0){	/* wipe tag right of bar */
+			wincleartag(w);
+			settag = TRUE;
+			m = 8;
+		}else
+		if(strncmp(p, "scroll", 6) == 0){	/* turn on automatic scrolling (writes to body only) */
+			w->noscroll = FALSE;
+			m = 6;
+		}else{
+			err = Ebadctl;
+			break;
+		}
+		while(p[m] == '\n')
+			m++;
+	}
+
+	if(isfbuf)
+		fbuffree(r);
+	else
+		free(r);
+	if(err)
+		n = 0;
+	fc.count = n;
+	respond(x, &fc, err);
+	if(settag)
+		winsettag(w);
+	if(scrdraw)
+		textscrdraw(&w->body);
+}
+
+void
+xfideventwrite(Xfid *x, Window *w)
+{
+	Fcall fc;
+	int m, n;
+	Rune *r;
+	char *err, *p, *q;
+	int isfbuf;
+	Text *t;
+	int c;
+	uint q0, q1;
+
+	err = nil;
+	isfbuf = TRUE;
+	if(x->fcall.count < RBUFSIZE)
+		r = fbufalloc();
+	else{
+		isfbuf = FALSE;
+		r = emalloc(x->fcall.count*UTFmax+1);
+	}
+	for(n=0; n<x->fcall.count; n+=m){
+		p = x->fcall.data+n;
+		w->owner = *p++;	/* disgusting */
+		c = *p++;
+		while(*p == ' ')
+			p++;
+		q0 = strtoul(p, &q, 10);
+		if(q == p)
+			goto Rescue;
+		p = q;
+		while(*p == ' ')
+			p++;
+		q1 = strtoul(p, &q, 10);
+		if(q == p)
+			goto Rescue;
+		p = q;
+		while(*p == ' ')
+			p++;
+		if(*p++ != '\n')
+			goto Rescue;
+		m = p-(x->fcall.data+n);
+		if('a'<=c && c<='z')
+			t = &w->tag;
+		else if('A'<=c && c<='Z')
+			t = &w->body;
+		else
+			goto Rescue;
+		if(q0>t->file->b.nc || q1>t->file->b.nc || q0>q1)
+			goto Rescue;
+
+		qlock(&row.lk);	/* just like mousethread */
+		switch(c){
+		case 'x':
+		case 'X':
+			execute(t, q0, q1, TRUE, nil);
+			break;
+		case 'l':
+		case 'L':
+			look3(t, q0, q1, TRUE);
+			break;
+		default:
+			qunlock(&row.lk);
+			goto Rescue;
+		}
+		qunlock(&row.lk);
+
+	}
+
+    Out:
+	if(isfbuf)
+		fbuffree(r);
+	else
+		free(r);
+	if(err)
+		n = 0;
+	fc.count = n;
+	respond(x, &fc, err);
+	return;
+
+    Rescue:
+	err = Ebadevent;
+	goto Out;
+}
+
+void
+xfidutfread(Xfid *x, Text *t, uint q1, int qid)
+{
+	Fcall fc;
+	Window *w;
+	Rune *r;
+	char *b, *b1;
+	uint q, off, boff;
+	int m, n, nr, nb;
+
+	w = t->w;
+	wincommit(w, t);
+	off = x->fcall.offset;
+	r = fbufalloc();
+	b = fbufalloc();
+	b1 = fbufalloc();
+	n = 0;
+	if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){
+		boff = w->utflastboff;
+		q = w->utflastq;
+	}else{
+		/* BUG: stupid code: scan from beginning */
+		boff = 0;
+		q = 0;
+	}
+	w->utflastqid = qid;
+	while(q<q1 && n<x->fcall.count){
+		/*
+		 * Updating here avoids partial rune problem: we're always on a
+		 * char boundary. The cost is we will usually do one more read
+		 * than we really need, but that's better than being n^2.
+		 */
+		w->utflastboff = boff;
+		w->utflastq = q;
+		nr = q1-q;
+		if(nr > BUFSIZE/UTFmax)
+			nr = BUFSIZE/UTFmax;
+		bufread(&t->file->b, q, r, nr);
+		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
+		if(boff >= off){
+			m = nb;
+			if(boff+m > off+x->fcall.count)
+				m = off+x->fcall.count - boff;
+			memmove(b1+n, b, m);
+			n += m;
+		}else if(boff+nb > off){
+			if(n != 0)
+				error("bad count in utfrune");
+			m = nb - (off-boff);
+			if(m > x->fcall.count)
+				m = x->fcall.count;
+			memmove(b1, b+(off-boff), m);
+			n += m;
+		}
+		boff += nb;
+		q += nr;
+	}
+	fbuffree(r);
+	fbuffree(b);
+	fc.count = n;
+	fc.data = b1;
+	respond(x, &fc, nil);
+	fbuffree(b1);
+}
+
+int
+xfidruneread(Xfid *x, Text *t, uint q0, uint q1)
+{
+	Fcall fc;
+	Window *w;
+	Rune *r, junk;
+	char *b, *b1;
+	uint q, boff;
+	int i, rw, m, n, nr, nb;
+
+	w = t->w;
+	wincommit(w, t);
+	r = fbufalloc();
+	b = fbufalloc();
+	b1 = fbufalloc();
+	n = 0;
+	q = q0;
+	boff = 0;
+	while(q<q1 && n<x->fcall.count){
+		nr = q1-q;
+		if(nr > BUFSIZE/UTFmax)
+			nr = BUFSIZE/UTFmax;
+		bufread(&t->file->b, q, r, nr);
+		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
+		m = nb;
+		if(boff+m > x->fcall.count){
+			i = x->fcall.count - boff;
+			/* copy whole runes only */
+			m = 0;
+			nr = 0;
+			while(m < i){
+				rw = chartorune(&junk, b+m);
+				if(m+rw > i)
+					break;
+				m += rw;
+				nr++;
+			}
+			if(m == 0)
+				break;
+		}
+		memmove(b1+n, b, m);
+		n += m;
+		boff += nb;
+		q += nr;
+	}
+	fbuffree(r);
+	fbuffree(b);
+	fc.count = n;
+	fc.data = b1;
+	respond(x, &fc, nil);
+	fbuffree(b1);
+	return q-q0;
+}
+
+void
+xfideventread(Xfid *x, Window *w)
+{
+	Fcall fc;
+	char *b;
+	int i, n;
+
+	i = 0;
+	x->flushed = FALSE;
+	while(w->nevents == 0){
+		if(i){
+			if(!x->flushed)
+				respond(x, &fc, "window shut down");
+			return;
+		}
+		w->eventx = x;
+		winunlock(w);
+		recvp(x->c);
+		winlock(w, 'F');
+		i++;
+	}
+
+	n = w->nevents;
+	if(n > x->fcall.count)
+		n = x->fcall.count;
+	fc.count = n;
+	fc.data = w->events;
+	respond(x, &fc, nil);
+	b = w->events;
+	w->events = estrdup(w->events+n);
+	free(b);
+	w->nevents -= n;
+}
+
+void
+xfidindexread(Xfid *x)
+{
+	Fcall fc;
+	int i, j, m, n, nmax, isbuf, cnt, off;
+	Window *w;
+	char *b;
+	Rune *r;
+	Column *c;
+
+	qlock(&row.lk);
+	nmax = 0;
+	for(j=0; j<row.ncol; j++){
+		c = row.col[j];
+		for(i=0; i<c->nw; i++){
+			w = c->w[i];
+			nmax += Ctlsize + w->tag.file->b.nc*UTFmax + 1;
+		}
+	}
+	nmax++;
+	isbuf = (nmax<=RBUFSIZE);
+	if(isbuf)
+		b = (char*)x->buf;
+	else
+		b = emalloc(nmax);
+	r = fbufalloc();
+	n = 0;
+	for(j=0; j<row.ncol; j++){
+		c = row.col[j];
+		for(i=0; i<c->nw; i++){
+			w = c->w[i];
+			/* only show the currently active window of a set */
+			if(w->body.file->curtext != &w->body)
+				continue;
+			winctlprint(w, b+n, 0);
+			n += Ctlsize;
+			m = min(RBUFSIZE, w->tag.file->b.nc);
+			bufread(&w->tag.file->b, 0, r, m);
+			m = n + snprint(b+n, nmax-n-1, "%.*S", m, r);
+			while(n<m && b[n]!='\n')
+				n++;
+			b[n++] = '\n';
+		}
+	}
+	qunlock(&row.lk);
+	off = x->fcall.offset;
+	cnt = x->fcall.count;
+	if(off > n)
+		off = n;
+	if(off+cnt > n)
+		cnt = n-off;
+	fc.count = cnt;
+	memmove(r, b+off, cnt);
+	fc.data = (char*)r;
+	if(!isbuf)
+		free(b);
+	respond(x, &fc, nil);
+	fbuffree(r);
+}
blob - /dev/null
blob + 4eb6eac9869b53fcafbf301a4f7f596c02ceb242 (mode 644)
--- /dev/null
+++ src/lib9/_p9translate.c
@@ -0,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * I don't want too many of these,
+ * but the ones we have are just too useful. 
+ */
+static struct {
+	char *old;
+	char *new;
+} replace[] = {
+	"#9", nil,	/* must be first */
+	"#d", "/dev/fd",
+};
+
+char*
+_p9translate(char *old)
+{
+	char *new;
+	int i, olen, nlen, len;
+
+	if(replace[0].new == nil){
+		replace[0].new = getenv("PLAN9");
+		if(replace[0].new == nil)
+			replace[0].new = "/usr/local/plan9";
+	}
+
+	for(i=0; i<nelem(replace); i++){
+		if(!replace[i].new)
+			continue;
+		olen = strlen(replace[i].old);
+		if(strncmp(old, replace[i].old, olen) != 0
+		|| (old[olen] != '\0' && old[olen] != '/'))
+			continue;
+		nlen = strlen(replace[i].new);
+		len = strlen(old)+nlen-olen;
+		new = malloc(len+1);
+		if(new == nil)
+			return nil;
+		strcpy(new, replace[i].new);
+		strcpy(new+nlen, old+olen);
+		assert(strlen(new) == len);
+		return new;
+	}
+	return old;
+}
blob - /dev/null
blob + 20b00c32744d1d1cf75af6196b1dbeb9b6905be9 (mode 644)
--- /dev/null
+++ src/lib9/access.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+char *_p9translate(char*);
+
+int
+p9access(char *xname, int what)
+{
+	int ret;
+	char *name;
+
+	if((name = _p9translate(xname)) == nil)
+		return -1;
+	ret = access(name, what);
+	if(name != xname)
+		free(name);
+	return ret;
+}
blob - /dev/null
blob + 29bc857c4601314b31ec167eddd28a7e551f0b0a (mode 644)
--- /dev/null
+++ src/lib9/getns.c
@@ -0,0 +1,74 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+/*
+ * Absent other hints, it works reasonably well to use
+ * the X11 display name as the name space identifier.
+ * This is how sam's B has worked since the early days.
+ * Since most programs using name spaces are also using X,
+ * this still seems reasonable.  Terminal-only sessions
+ * can set $NAMESPACE.
+ */
+static char*
+nsfromdisplay(void)
+{
+	int fd;
+	Dir *d;
+	char *disp, *p;
+
+	if((disp = getenv("DISPLAY")) == nil){
+		werrstr("$DISPLAY not set");
+		return nil;
+	}
+
+	/* canonicalize: xxx:0.0 => xxx:0 */
+	p = strrchr(disp, ':');
+	if(p){
+		p++;
+		while(isdigit((uchar)*p))
+			p++;
+		if(strcmp(p, ".0") == 0)
+			*p = 0;
+	}
+
+	p = smprint("/tmp/ns.%s.%s", getuser(), disp);
+	free(disp);
+	if(p == nil){
+		werrstr("out of memory");
+		return p;
+	}
+	if((fd=create(p, OREAD, DMDIR|0700)) >= 0){
+		close(fd);
+		return p;
+	}
+	if((d = dirstat(p)) == nil){
+		free(d);
+		werrstr("stat %s: %r", p);
+		free(p);
+		return nil;
+	}
+	if((d->mode&0777) != 0700 || strcmp(d->uid, getuser()) != 0){
+		werrstr("bad name space dir %s", p);
+		free(p);
+		free(d);
+		return nil;
+	}
+	free(d);
+	return p;
+}
+
+char*
+getns(void)
+{
+	char *ns;
+
+	ns = getenv("NAMESPACE");
+	if(ns == nil)
+		ns = nsfromdisplay();
+	if(ns == nil){
+		werrstr("$NAMESPACE not set, %r");
+		return nil;
+	}
+	return ns;
+}
blob - /dev/null
blob + b75d2f07f5d7cbfb7a3f170591cfa14822729b59 (mode 644)
--- /dev/null
+++ src/lib9/malloc.c
@@ -0,0 +1,11 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+void*
+p9malloc(ulong n)
+{
+	if(n == 0)
+		n++;
+	return malloc(n);
+}
blob - /dev/null
blob + bb597e8f2171fcf792f8ac5b591c0ecd5771981b (mode 644)
--- /dev/null
+++ src/lib9/open.c
@@ -0,0 +1,38 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+extern char* _p9translate(char*);
+
+int
+p9open(char *xname, int mode)
+{
+	char *name;
+	int cexec, rclose;
+	int fd, umode;
+
+	umode = mode&3;
+	cexec = mode&OCEXEC;
+	rclose = mode&ORCLOSE;
+	mode &= ~(3|OCEXEC|ORCLOSE);
+	if(mode&OTRUNC){
+		umode |= O_TRUNC;
+		mode ^= OTRUNC;
+	}
+	if(mode){
+		werrstr("mode not supported");
+		return -1;
+	}
+	if((name = _p9translate(xname)) == nil)
+		return -1;
+	fd = open(name, umode);
+	if(fd >= 0){
+		if(cexec)
+			fcntl(fd, F_SETFL, FD_CLOEXEC);
+		if(rclose)
+			remove(name);
+	}
+	if(name != xname)
+		free(name);
+	return fd;
+}
blob - /dev/null
blob + f9fe2420e57a5ca68c6c61a38f3e54f4ac7879e7 (mode 644)
--- /dev/null
+++ src/lib9/pipe.c
@@ -0,0 +1,10 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+#include <sys/socket.h>
+
+int
+p9pipe(int fd[2])
+{
+	return socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
+}
blob - /dev/null
blob + 35ba3167a469e98c023c74fafb4d99380fe00a7c (mode 644)
--- /dev/null
+++ src/lib9/post9p.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+
+int
+post9pservice(int fd, char *name)
+{
+	int i;
+	char *ns, *s;
+	Waitmsg *w;
+
+	if((ns = getns()) == nil)
+		return -1;
+	s = smprint("unix!%s/%s", ns, name);
+	free(ns);
+	if(s == nil)
+		return -1;
+	switch(rfork(RFPROC|RFFDG)){
+	case -1:
+		return -1;
+	case 0:
+		dup(fd, 0);
+		dup(fd, 1);
+		for(i=3; i<20; i++)
+			close(i);
+		execlp("9pserve", "9pserve", "-u", s, (char*)0);
+		fprint(2, "exec 9pserve: %r\n");
+		_exits("exec");
+	default:
+		w = wait();
+		close(fd);
+		free(s);
+		if(w->msg && w->msg[0]){
+			free(w);
+			werrstr("9pserve failed");
+			return -1;
+		}
+		free(w);
+		return 0;
+	}
+}
blob - /dev/null
blob + b3a2448ff895e19894227093fd5685d641f1a47a (mode 644)
--- /dev/null
+++ src/lib9/sendfd.c
@@ -0,0 +1,79 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+
+typedef struct Sendfd Sendfd;
+struct Sendfd {
+	struct cmsghdr cmsg;
+	int fd;
+};
+
+int
+sendfd(int s, int fd)
+{
+	char buf[1];
+	struct iovec iov;
+	struct msghdr msg;
+	int n;
+	Sendfd sfd;
+
+	buf[0] = 0;
+	iov.iov_base = buf;
+	iov.iov_len = 1;
+
+	memset(&msg, 0, sizeof msg);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	sfd.cmsg.cmsg_len = sizeof sfd;
+	sfd.cmsg.cmsg_level = SOL_SOCKET;
+	sfd.cmsg.cmsg_type = SCM_RIGHTS;
+	sfd.fd = fd;
+
+	msg.msg_control = &sfd;
+	msg.msg_controllen = sizeof sfd;
+
+	if((n=sendmsg(s, &msg, 0)) != iov.iov_len)
+		return -1;
+	return 0;
+}
+
+int
+recvfd(int s)
+{
+	int n;
+	char buf[1];
+	struct iovec iov;
+	struct msghdr msg;
+	Sendfd sfd;
+
+	iov.iov_base = buf;
+	iov.iov_len = 1;
+
+	memset(&msg, 0, sizeof msg);
+	msg.msg_name = 0;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	memset(&sfd, 0, sizeof sfd);
+	sfd.fd = -1;
+	sfd.cmsg.cmsg_len = sizeof sfd;
+	sfd.cmsg.cmsg_level = SOL_SOCKET;
+	sfd.cmsg.cmsg_type = SCM_RIGHTS;
+
+	msg.msg_control = &sfd;
+	msg.msg_controllen = sizeof sfd;
+
+	if((n=recvmsg(s, &msg, 0)) < 0)
+		return -1;
+	if(n==0 && sfd.fd==-1){
+		werrstr("eof in recvfd");
+		return -1;
+	}
+	return sfd.fd;
+}
blob - /dev/null
blob + 843f7558cadb8033a33a0c7dfb3be5e0daff3952 (mode 644)
--- /dev/null
+++ src/libbio/_lib9.h
@@ -0,0 +1,12 @@
+#include <fmt.h>
+#include <fcntl.h>
+#include	<string.h>
+#include	<unistd.h>
+#include <stdlib.h>
+
+#define OREAD O_RDONLY
+#define OWRITE O_WRONLY
+
+#include <utf.h>
+
+#define nil ((void*)0)
blob - /dev/null
blob + 77a03a0fba4efabbec6efe3b7cc1b59819e8b43a (mode 644)
--- /dev/null
+++ src/libfs/ns.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#include <ctype.h>
+
+Fsys*
+nsmount(char *name, char *aname)
+{
+	char *addr, *ns;
+	int fd;
+	Fsys *fs;
+
+	ns = getns();
+	if(ns == nil)
+		return nil;
+
+	addr = smprint("unix!%s/%s", ns, name);
+	free(ns);
+	if(addr == nil)
+		return nil;
+
+	fd = dial(addr, 0, 0, 0);
+	if(fd < 0){
+		werrstr("dial %s: %r", addr);
+		return nil;
+	}
+
+	fs = fsmount(fd, aname);
+	if(fs == nil){
+		close(fd);
+		return nil;
+	}
+
+	return fs;
+}
blob - /dev/null
blob + 9e48c791c4c2cdf5eb4d14f882a32dfd03659d40 (mode 644)
--- /dev/null
+++ src/libfs/openfd.c
@@ -0,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#include "fsimpl.h"
+
+int
+fsopenfd(Fsys *fs, char *name, int mode)
+{
+	Fid *fid;
+	Fcall tx, rx;
+
+	if((fid = fswalk(fs->root, name)) == nil)
+		return -1;
+	tx.type = Topenfd;
+	tx.fid = fid->fid;
+	tx.mode = mode&~OCEXEC;
+	if(fsrpc(fs, &tx, &rx, 0) < 0){
+		fsclose(fid);
+		return -1;
+	}
+	_fsputfid(fid);
+	if(mode&OCEXEC && rx.unixfd>=0)
+		fcntl(rx.unixfd, F_SETFL, FD_CLOEXEC);
+	return rx.unixfd;
+}