Fighting the good fight.

Move libfmt, libutf into subdirectories of lib9.

Add poll-based socket i/o to libthread, so that we can
avoid using multiple procs when possible, thus removing
dependence on crappy pthreads implementations.

Convert samterm, acme to the single-proc libthread.

Bring libcomplete, acme up-to-date w.r.t. Plan 9 distribution.
diff --git a/src/cmd/acme/acme.c b/src/cmd/acme/acme.c
index 0ac66e1..87924ca 100644
--- a/src/cmd/acme/acme.c
+++ b/src/cmd/acme/acme.c
@@ -59,7 +59,6 @@
 {
 	int i;
 	char *p, *loadfile;
-	char buf[256];
 	Column *c;
 	int ncol;
 	Display *d;
@@ -70,6 +69,9 @@
 
 	loadfile = nil;
 	ARGBEGIN{
+	case 'a':
+		globalautoindent = TRUE;
+		break;
 	case 'b':
 		bartflag = TRUE;
 		break;
@@ -98,7 +100,7 @@
 		break;
 	default:
 	Usage:
-		fprint(2, "usage: acme -c ncol -f fontname -F fixedwidthfontname -l loadfile\n");
+		fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwidthfontname -l loadfile\n");
 		exits("usage");
 	}ARGEND
 
@@ -183,7 +185,7 @@
 		fprint(2, "acme: can't initialize plumber: %r\n");
 	else{
 		cplumb = chancreate(sizeof(Plumbmsg*), 0);
-		proccreate(plumbproc, nil, STACK);
+		threadcreate(plumbproc, nil, STACK);
 	}
 	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
 
@@ -315,7 +317,7 @@
 	USED(v);
 	threadsetname("acmeerrorproc");
 	buf = emalloc(8192+1);
-	while((n=read(errorfd, buf, 8192)) >= 0){
+	while((n=threadread(errorfd, buf, 8192)) >= 0){
 		buf[n] = '\0';
 		sendp(cerr, estrdup(buf));
 	}
@@ -324,8 +326,7 @@
 void
 acmeerrorinit(void)
 {
-	int fd, pfd[2];
-	char buf[64];
+	int pfd[2];
 
 	if(pipe(pfd) < 0)
 		error("can't create pipe");
@@ -351,7 +352,7 @@
 	errorfd = pfd[1];
 	if(errorfd < 0)
 		error("can't re-open acmeerror file");
-	proccreate(acmeerrorproc, nil, STACK);
+	threadcreate(acmeerrorproc, nil, STACK);
 }
 
 void
@@ -362,7 +363,7 @@
 	USED(v);
 	threadsetname("plumbproc");
 	for(;;){
-		m = plumbrecv(plumbeditfd);
+		m = threadplumbrecv(plumbeditfd);
 		if(m == nil)
 			threadexits(nil);
 		sendp(cplumb, m);
@@ -399,6 +400,7 @@
 				winlock(t->w, 'K');
 				wincommit(t->w, t);
 				winunlock(t->w);
+				flushwarnings(1);
 				flushimage(display, 1);
 			}
 			alts[KTimer].c = nil;
@@ -425,6 +427,7 @@
 			}
 			if(nbrecv(keyboardctl->c, &r) > 0)
 				goto casekeyboard;
+			flushwarnings(1);
 			flushimage(display, 1);
 			break;
 		}
@@ -467,6 +470,7 @@
 			draw(screen, screen->r, display->white, nil, ZP);
 			scrlresize();
 			rowresize(&row, screen->clipr);
+			flushwarnings(1);
 			flushimage(display, 1);
 			break;
 		case MPlumb:
@@ -477,6 +481,7 @@
 				else if(strcmp(act, "showdata")==0)
 					plumbshow(pm);
 			}
+			flushwarnings(1);
 			flushimage(display, 1);
 			plumbfree(pm);
 			break;
@@ -562,6 +567,7 @@
 				goto Continue;
 			}
     Continue:
+			flushwarnings(0);
 			flushimage(display, 1);
 			qunlock(&row.lk);
 			break;
@@ -916,36 +922,48 @@
 void
 acmeputsnarf(void)
 {
-	int fd, i, n;
+	int i, n;
+	Fmt f;
+	char *s;
 
-	if(snarffd<0 || snarfbuf.nc==0)
+	if(snarfbuf.nc==0)
 		return;
 	if(snarfbuf.nc > MAXSNARF)
 		return;
-	fd = open("/dev/snarf", OWRITE);
-	if(fd < 0)
-		return;
+
+	fmtstrinit(&f);
 	for(i=0; i<snarfbuf.nc; i+=n){
 		n = snarfbuf.nc-i;
 		if(n >= NSnarf)
 			n = NSnarf;
 		bufread(&snarfbuf, i, snarfrune, n);
-		if(fprint(fd, "%.*S", n, snarfrune) < 0)
+		if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
 			break;
 	}
-	close(fd);
+	s = fmtstrflush(&f);
+	if(s && s[0])
+		putsnarf(s);
+	free(s);
 }
 
 void
-acmegetsnarf()
+acmegetsnarf(void)
 {
-	int nulls;
+	char *s;
+	int nb, nr, nulls, len;
+	Rune *r;
 
-	if(snarfbuf.nc > MAXSNARF)
+	s = getsnarf();
+	if(s == nil || s[0]==0){
+		free(s);
 		return;
-	if(snarffd < 0)
-		return;
-	seek(snarffd, 0, 0);
+	}
+
+	len = strlen(s);
+	r = runemalloc(len+1);
+	cvttorunes(s, len, r, &nb, &nr, &nulls);
 	bufreset(&snarfbuf);
-	bufload(&snarfbuf, 0, snarffd, &nulls);
+	bufinsert(&snarfbuf, 0, r, nr);
+	free(r);
+	free(s);
 }
diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h
index b2a443d..9101ca3 100644
--- a/src/cmd/acme/dat.h
+++ b/src/cmd/acme/dat.h
@@ -232,6 +232,7 @@
 	uchar	isscratch;
 	uchar	filemenu;
 	uchar	dirty;
+	uchar	autoindent;
 	int		id;
 	Range	addr;
 	Range	limit;
@@ -486,6 +487,7 @@
 	Collecting,
 };
 
+uint		globalincref;
 uint		seq;
 uint		maxtab;	/* size of a tab, in units of the '0' character */
 
@@ -524,10 +526,11 @@
 Image		*textcols[NCOL];
 int			plumbsendfd;
 int			plumbeditfd;
-extern char		wdir[];
+extern char		wdir[]; /* must use extern because no dimension given */
 int			editing;
 int			erroutfd;
 int			messagesize;		/* negotiated in 9P version setup */
+int			globalautoindent;
 
 Channel	*ckeyboard;	/* chan(Rune)[10] */
 Channel	*cplumb;		/* chan(Plumbmsg*) */
diff --git a/src/cmd/acme/ecmd.c b/src/cmd/acme/ecmd.c
index 677bafb..0dfae90 100644
--- a/src/cmd/acme/ecmd.c
+++ b/src/cmd/acme/ecmd.c
@@ -596,7 +596,7 @@
 
 	r = skipbl(cr, ncr, &n);
 	if(n == 0)
-		editerror("no command specified for >");
+		editerror("no command specified for %c", cmd);
 	w = nil;
 	if(state == Inserting){
 		w = t->w;
@@ -949,12 +949,15 @@
 	/*
 	 * 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.
+	 * the shenanigans.  note this with globalincref so that any
+	 * newly created windows start with an extra reference.
 	 */
 	allwindows(alllocker, (void*)1);
+	globalincref = 1;
 	for(i=0; i<loopstruct.nw; i++)
 		cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
 	allwindows(alllocker, (void*)0);
+	globalincref = 0;
 	free(loopstruct.w);
 	loopstruct.w = nil;
 
diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c
index 9c29dc1..d81153e 100644
--- a/src/cmd/acme/exec.c
+++ b/src/cmd/acme/exec.c
@@ -462,7 +462,7 @@
 		dir.nr = 0;
 		if(n>0 && arg[0]!='/'){
 			dir = dirname(t, nil, 0);
-			if(n==1 && dir.r[0]=='.'){	/* sigh */
+			if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
 				free(dir.r);
 				dir.r = nil;
 				dir.nr = 0;
@@ -606,15 +606,15 @@
 			f->qidpath = d->qid.path;
 			f->mtime = d->mtime;
 			if(f->unread)
-				warningew(w, nil, "%s not written; file already exists\n", name);
+				warning(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);
+				warning(nil, "%s modified%s%s since last read\n", name, d->muid[0]?" by ":"", d->muid);
 			goto Rescue1;
 		}
 	}
 	fd = create(name, OWRITE, 0666);
 	if(fd < 0){
-		warningew(w, nil, "can't create file %s: %r\n", name);
+		warning(nil, "can't create file %s: %r\n", name);
 		goto Rescue1;
 	}
 	r = fbufalloc();
@@ -623,7 +623,7 @@
 	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);
+		warning(nil, "%s not written; file is append only\n", name);
 		goto Rescue2;
 	}
 
@@ -634,7 +634,7 @@
 		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);
+			warning(nil, "can't write file %s: %r\n", name);
 			goto Rescue2;
 		}
 	}
@@ -701,7 +701,7 @@
 	f = w->body.file;
 	name = getname(&w->body, argt, arg, narg, TRUE);
 	if(name == nil){
-		warningew(w, nil, "no file name\n");
+		warning(nil, "no file name\n");
 		return;
 	}
 	namer = bytetorune(name, &nname);
@@ -1163,6 +1163,58 @@
 	}
 }
 
+static Rune LON[] = { 'O', 'N', 0 };
+static Rune LOFF[] = { 'O', 'F', 'F', 0 };
+static Rune Lon[] = { 'o', 'n', 0 };
+
+static int
+indentval(Rune *s, int n)
+{
+	if(n < 2)
+		return -1;
+	if(runestrncmp(s, LON, n) == 0){
+		globalautoindent = TRUE;
+		warning(nil, "Indent ON\n");
+		return -2;
+	}
+	if(runestrncmp(s, LOFF, n) == 0){
+		globalautoindent = FALSE;
+		warning(nil, "Indent OFF\n");
+		return -2;
+	}
+	return runestrncmp(s, Lon, n) == 0;
+}
+
+void
+indent(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, len, autoindent;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	if(et==nil || et->w==nil)
+		return;
+	w = et->w;
+	autoindent = -1;
+	getarg(argt, FALSE, TRUE, &r, &len);
+	if(r!=nil && len>0)
+		autoindent = indentval(r, len);
+	else{
+		a = findbl(arg, narg, &na);
+		if(a != arg)
+			autoindent = indentval(arg, narg-na);
+	}
+	if(autoindent >= 0)
+		w->autoindent = autoindent;
+	if(autoindent != 2)
+		warning(nil, "%.*S: Indent %s\n", w->body.file->nname, w->body.file->name,
+			w->autoindent ? "on" : "off");
+}
+
 void
 tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
 {
@@ -1375,7 +1427,7 @@
 	av[ac++] = arg;
 	av[ac] = nil;
 	c->av = av;
-	procexec(cpid, sfd, av[0], av);
+	threadexec(cpid, sfd, av[0], av);
 /* libthread uses execvp so no need to do this */
 #if 0
 	e = av[0];
@@ -1419,10 +1471,10 @@
 			c->text = news;
 		}
 	}
-	procexecl(cpid, sfd, "rc", "rc", "-c", t, nil);
+	threadexecl(cpid, sfd, "rc", "rc", "-c", t, nil);
 
    Fail:
-	/* procexec hasn't happened, so send a zero */
+	/* threadexec hasn't happened, so send a zero */
 	close(sfd[0]);
 	close(sfd[1]);
 	if(sfd[2] != sfd[1])
@@ -1482,7 +1534,7 @@
 	arg[7] = c;
 	arg[8] = cpid;
 	arg[9] = (void*)iseditcmd;
-	proccreate(runproc, arg, STACK);
+	threadcreate(runproc, arg, STACK);
 	/* mustn't block here because must be ready to answer mount() call in run() */
 	arg = emalloc(2*sizeof(void*));
 	arg[0] = c;
diff --git a/src/cmd/acme/fns.h b/src/cmd/acme/fns.h
index 69bbbb3..a73a7ec 100644
--- a/src/cmd/acme/fns.h
+++ b/src/cmd/acme/fns.h
@@ -27,7 +27,7 @@
 void	allwindows(void(*)(Window*, void*), void*);
 uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
 
-Window*	errorwin(Mntdir*, int, Window*);
+Window*	errorwin(Mntdir*, int);
 Runestr cleanrname(Runestr);
 void	run(Window*, char*, Rune*, int, int, char*, char*, int);
 void fsysclose(void);
@@ -85,7 +85,8 @@
 int	expand(Text*, uint, uint, Expand*);
 Rune*	skipbl(Rune*, int, int*);
 Rune*	findbl(Rune*, int, int*);
-char*	edittext(Window*, int, Rune*, int);	
+char*	edittext(Window*, int, Rune*, int);
+void		flushwarnings(int);
 
 #define	runemalloc(a)		(Rune*)emalloc((a)*sizeof(Rune))
 #define	runerealloc(a, b)	(Rune*)erealloc((a), (b)*sizeof(Rune))
diff --git a/src/cmd/acme/fsys.c b/src/cmd/acme/fsys.c
index d220bde..f178f86 100644
--- a/src/cmd/acme/fsys.c
+++ b/src/cmd/acme/fsys.c
@@ -111,8 +111,7 @@
 fsysinit(void)
 {
 	int p[2];
-	int n, fd;
-	char buf[256], *u;
+	char *u;
 
 	if(pipe(p) < 0)
 		error("can't create pipe");
@@ -122,7 +121,7 @@
 	fmtinstall('F', fcallfmt);
 	if((u = getuser()) != nil)
 		user = estrdup(u);
-	proccreate(fsysproc, nil, STACK);
+	threadcreate(fsysproc, nil, STACK);
 }
 
 void
@@ -138,7 +137,7 @@
 	x = nil;
 	for(;;){
 		buf = emalloc(messagesize+UTFmax);	/* overflow for appending partial rune in xfidwrite */
-		n = read9pmsg(sfd, buf, messagesize);
+		n = threadread9pmsg(sfd, buf, messagesize);
 		if(n <= 0){
 			if(closing)
 				break;
@@ -255,7 +254,11 @@
 		x->buf = emalloc(messagesize);
 	n = convS2M(t, x->buf, messagesize);
 	if(n <= 0)
+{
+fprint(2, "convert error (n=%d, msgsize %d): have %F\n", n, messagesize, &x->fcall);
+fprint(2, "\tresponse: %F\n", t);
 		error("convert error in convS2M");
+}
 	if(write(sfd, x->buf, n) != n)
 		error("write error in respond");
 	free(x->buf);
diff --git a/src/cmd/acme/look.c b/src/cmd/acme/look.c
index 9f54f9e..ca7eba4 100644
--- a/src/cmd/acme/look.c
+++ b/src/cmd/acme/look.c
@@ -320,60 +320,18 @@
 	return FALSE;
 }
 
+/* Runestr wrapper for cleanname */
 Runestr
 cleanrname(Runestr rs)
 {
-	int i, j, found;
-	Rune *b;
-	int n;
-	static Rune Lslashdotdot[] = { '/', '.', '.', 0 };
+	char *s;
+	int nb, nulls;
 
-	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};
+	s = runetobyte(rs.r, rs.nr);
+	cleanname(s);
+	cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls);
+	free(s);
+	return rs;
 }
 
 Runestr
@@ -407,6 +365,11 @@
 	Window *w;
 	char buf[128];
 	Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+	Rune Lusrinclude[] = { '/', 'u', 's', 'r', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+	Rune Lusrlocalinclude[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l', 
+			'/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+	Rune Lusrlocalplan9include[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l', 
+			'/', 'p', 'l', 'a', 'n', '9', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
 	Runestr file;
 	int i;
 
@@ -429,6 +392,12 @@
 
 	if(file.r == nil)
 		file = includefile(Lsysinclude, r, n);
+	if(file.r == nil)
+		file = includefile(Lusrlocalplan9include, r, n);
+	if(file.r == nil)
+		file = includefile(Lusrlocalinclude, r, n);
+	if(file.r == nil)
+		file = includefile(Lusrinclude, r, n);
 	if(file.r==nil && objdir!=nil)
 		file = includefile(objdir, r, n);
 	if(file.r == nil)
@@ -702,13 +671,16 @@
 		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)
+		if(ow != nil){
 			for(i=ow->nincl; --i>=0; ){
 				n = runestrlen(ow->incl[i]);
 				rp = runemalloc(n);
 				runemove(rp, ow->incl[i], n);
 				winaddincl(w, rp, n);
 			}
+			w->autoindent = ow->autoindent;
+		}else
+			w->autoindent = globalautoindent;
 	}
 	if(e->a1 == e->a0)
 		eval = FALSE;
diff --git a/src/cmd/acme/mkfile b/src/cmd/acme/mkfile
index cb72fea..86a3fbd 100644
--- a/src/cmd/acme/mkfile
+++ b/src/cmd/acme/mkfile
@@ -1,3 +1,5 @@
+# Acme is up-to-date w.r.t. sources as of 29 February 2004
+
 PLAN9=../../..
 <$PLAN9/src/mkhdr
 
@@ -36,6 +38,6 @@
 
 <$PLAN9/src/mkone
 
-LDFLAGS=$LDFLAGS -lcomplete -lplumb -lfs -lmux -lthread -lframe -ldraw -lbio -l9 -lfmt -lutf -L$X11/lib -lX11
+LDFLAGS=$LDFLAGS -lcomplete -lplumb -lfs -lmux -lthread -lframe -ldraw -lbio -l9 -L$X11/lib -lX11
 
 edit.$O ecmd.$O elog.$O:	edit.h
diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c
index 0b9f7cc..8bdf0b7 100644
--- a/src/cmd/acme/text.c
+++ b/src/cmd/acme/text.c
@@ -537,7 +537,7 @@
 	q = q0;
 	while(q > 0){
 		r = textreadc(t, q-1);
-		if(r<=' ')
+		if(r <= ' ')
 			break;
 		if(oneelement && r=='/')
 			break;
@@ -608,10 +608,11 @@
 	}
 
 	if(!c->advance){
-		warning(nil, "%.*S%s%.*S*\n",
+		warning(nil, "%.*S%s%.*S*%s\n",
 			dir.nr, dir.r,
 			dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
-			nstr, str);
+			nstr, str,
+			c->nmatch ? "" : ": no matches in:");
 		for(i=0; i<c->nfile; i++)
 			warning(nil, " %s\n", c->filename[i]);
 	}
@@ -643,25 +644,45 @@
 	rp = &r;
 	switch(r){
 	case Kleft:
-		if(t->q0 > 0)
+		if(t->q0 > 0){
+			textcommit(t, TRUE);
 			textshow(t, t->q0-1, t->q0-1, TRUE);
+		}
 		return;
 	case Kright:
-		if(t->q1 < t->file->b.nc)
+		if(t->q1 < t->file->b.nc){
+			textcommit(t, TRUE);
 			textshow(t, t->q1+1, t->q1+1, TRUE);
+		}
 		return;
 	case Kdown:
+		n = t->fr.maxlines/3;
+		goto case_Down;
 	case Kpgdown:
-		n = t->fr.maxlines/2;
+		n = 2*t->fr.maxlines/3;
+	case_Down:
 		q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
 		textsetorigin(t, q0, FALSE);
 		return;
 	case Kup:
+		n = t->fr.maxlines/3;
+		goto case_Up;
 	case Kpgup:
-		n = t->fr.maxlines/2;
+		n = 2*t->fr.maxlines/3;
+	case_Up:
 		q0 = textbacknl(t, t->org, n);
 		textsetorigin(t, q0, FALSE);
 		return;
+	case Khome:
+		textshow(t, 0, 0, FALSE);
+		return;
+	case Kend:
+		if(t->w)
+			wincommit(t->w, t);
+		else
+			textcommit(t, TRUE);
+		textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
+		return;
 	}
 	if(t->what == Body){
 		seq++;
@@ -734,6 +755,21 @@
 		for(i=0; i<t->file->ntext; i++)
 			textfill(t->file->text[i]);
 		return;
+	case '\n':
+		if(t->w->autoindent){
+			/* find beginning of previous line using backspace code */
+			nnb = textbswidth(t, 0x15); /* ^U case */
+			rp = runemalloc(nnb + 1);
+			nr = 0;
+			rp[nr++] = r;
+			for(i=0; i<nnb; i++){
+				r = textreadc(t, t->q0-nnb+i);
+				if(r != ' ' && r != '\t')
+					break;
+				rp[nr++] = r;
+			}
+		}
+		break; /* fall through to normal code */
 	}
 	/* otherwise ordinary character; just insert, typically in caches of all texts */
 	for(i=0; i<t->file->ntext; i++){
diff --git a/src/cmd/acme/time.c b/src/cmd/acme/time.c
index b281d68..f80891a 100644
--- a/src/cmd/acme/time.c
+++ b/src/cmd/acme/time.c
@@ -50,7 +50,7 @@
 	nt = 0;
 	old = msec();
 	for(;;){
-		sleep(1);	/* will sleep minimum incr */
+		threadsleep(1);	/* will sleep minimum incr */
 		new = msec();
 		dt = new-old;
 		old = new;
@@ -98,7 +98,7 @@
 timerinit(void)
 {
 	ctimer = chancreate(sizeof(Timer*), 100);
-	proccreate(timerproc, nil, STACK);
+	threadcreate(timerproc, nil, STACK);
 }
 
 Timer*
diff --git a/src/cmd/acme/util.c b/src/cmd/acme/util.c
index 8e3cfa2..180b2bd 100644
--- a/src/cmd/acme/util.c
+++ b/src/cmd/acme/util.c
@@ -62,9 +62,11 @@
 	int i, n;
 	static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
 
-	r = runemalloc(ndir+7);
-	if(n = ndir)	/* assign = */
+	r = runemalloc(ndir+8);
+	if(n = ndir){	/* assign = */
 		runemove(r, dir, ndir);
+		r[n++] = L'/';
+	}
 	runemove(r+n, Lpluserrors, 7);
 	n += 7;
 	w = lookfile(r, n);
@@ -83,12 +85,13 @@
 		runemove(r, incl[i], n);
 		winaddincl(w, r, n);
 	}
+	w->autoindent = globalautoindent;
 	return w;
 }
 
 /* make new window, if necessary; return with it locked */
 Window*
-errorwin(Mntdir *md, int owner, Window *e)
+errorwin(Mntdir *md, int owner)
 {
 	Window *w;
 
@@ -97,51 +100,100 @@
 			w = errorwin1(nil, 0, nil, 0);
 		else
 			w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
-		if(w != e)
-			winlock(w, owner);
+		winlock(w, owner);
 		if(w->col != nil)
 			break;
 		/* window was deleted too fast */
-		if(w != e)
-			winunlock(w);
+		winunlock(w);
 	}
 	return w;
 }
 
-static void
-printwarning(Window *ew, Mntdir *md, Rune *r)
+typedef struct Warning Warning;
+
+struct Warning{
+	Mntdir *md;
+	Buffer buf;
+	Warning *next;
+};
+
+static Warning *warnings;
+
+static
+void
+addwarningtext(Mntdir *md, Rune *r, int nr)
 {
-	int nr, q0, owner;
+	Warning *warn;
+	
+	for(warn = warnings; warn; warn=warn->next){
+		if(warn->md == md){
+			bufinsert(&warn->buf, warn->buf.nc, r, nr);
+			return;
+		}
+	}
+	warn = emalloc(sizeof(Warning));
+	warn->next = warnings;
+	warnings = warn;
+	bufinsert(&warn->buf, 0, r, nr);
+}
+
+void
+flushwarnings(int dolock)
+{
+	Warning *warn, *next;
 	Window *w;
 	Text *t;
+	int owner, nr, q0, n;
+	Rune *r;
 
-	if(r == nil)
-		error("runevsmprint failed");
-	nr = runestrlen(r);
-
+	if(dolock)
+		qlock(&row.lk);
 	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()");
+			error("initializing columns in flushwarnings()");
 	}
 
-	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)
+	for(warn=warnings; warn; warn=next) {
+		w = errorwin(warn->md, 'E');
+		t = &w->body;
+		owner = w->owner;
+		if(owner == 0)
+			w->owner = 'E';
+		wincommit(w, t);
+		/*
+		 * Most commands don't generate much output. For instance,
+		 * Edit ,>cat goes through /dev/cons and is already in blocks
+		 * because of the i/o system, but a few can.  Edit ,p will
+		 * put the entire result into a single hunk.  So it's worth doing
+		 * this in blocks (and putting the text in a buffer in the first
+		 * place), to avoid a big memory footprint.
+		 */
+		r = fbufalloc();
+		q0 = t->file->b.nc;
+		for(n = 0; n < warn->buf.nc; n += nr){
+			nr = warn->buf.nc - n;
+			if(nr > RBUFSIZE)
+				nr = RBUFSIZE;
+			bufread(&warn->buf, n, r, nr);
+			textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
+		}
+		textshow(t, q0, t->file->b.nc, 1);
+		free(r);
+		winsettag(t->w);
+		textscrdraw(t);
+		w->owner = owner;
+		w->dirty = FALSE;
 		winunlock(w);
-	free(r);
+		bufclose(&warn->buf);
+		next = warn->next;
+		free(warn);
+	}
+	warnings = nil;
+	if(dolock)
+		qunlock(&row.lk);
 }
 
 void
@@ -153,23 +205,9 @@
 	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);
+	if(r == nil)
+		error("runevsmprint failed");
+	addwarningtext(md, r, runestrlen(r));
 }
 
 int
diff --git a/src/cmd/acme/wind.c b/src/cmd/acme/wind.c
index 06a815e..90c4407 100644
--- a/src/cmd/acme/wind.c
+++ b/src/cmd/acme/wind.c
@@ -26,6 +26,8 @@
 	w->body.w = w;
 	w->id = ++winid;
 	incref(&w->ref);
+	if(globalincref)
+		incref(&w->ref);
 	w->ctlfid = ~0;
 	w->utflastqid = -1;
 	r1 = r;
@@ -141,7 +143,7 @@
 	int i;
 	File *f;
 
-fprint(2, "winlock %p %d %lux\n", w, owner, getcallerpc(&w));
+//fprint(2, "winlock %p %d %lux\n", w, owner, getcallerpc(&w));
 	f = w->body.file;
 	for(i=0; i<f->ntext; i++)
 		winlock1(f->text[i]->w, owner);
@@ -153,7 +155,7 @@
 	int i;
 	File *f;
 
-fprint(2, "winunlock %p %lux\n", w, getcallerpc(&w));
+//fprint(2, "winunlock %p %lux\n", w, getcallerpc(&w));
 	f = w->body.file;
 	for(i=0; i<f->ntext; i++){
 		w = f->text[i]->w;
diff --git a/src/cmd/acme/xfid.c b/src/cmd/acme/xfid.c
index 13af739..b3bef2c 100644
--- a/src/cmd/acme/xfid.c
+++ b/src/cmd/acme/xfid.c
@@ -383,7 +383,7 @@
 	x->fcall.data[x->fcall.count] = 0;
 	switch(qid){
 	case Qcons:
-		w = errorwin(x->f->mntdir, 'X', nil);
+		w = errorwin(x->f->mntdir, 'X');
 		t=&w->body;
 		goto BodyTag;
 
@@ -543,6 +543,7 @@
 	}
 	if(w)
 		winunlock(w);
+	flushwarnings(1);
 }
 
 void
@@ -813,6 +814,7 @@
 			qunlock(&row.lk);
 			goto Rescue;
 		}
+		flushwarnings(0);
 		qunlock(&row.lk);
 
 	}
@@ -1030,6 +1032,7 @@
 			b[n++] = '\n';
 		}
 	}
+	flushwarnings(0);
 	qunlock(&row.lk);
 	off = x->fcall.offset;
 	cnt = x->fcall.count;