completion, pageup/pagedown
diff --git a/src/cmd/acme/acme.c b/src/cmd/acme/acme.c
index d9412c4..0ac66e1 100644
--- a/src/cmd/acme/acme.c
+++ b/src/cmd/acme/acme.c
@@ -138,6 +138,7 @@
 
 	d = display;
 	font = d->defaultfont;
+//assert(font);
 
 	reffont.f = font;
 	reffonts[0] = &reffont;
@@ -790,6 +791,7 @@
 			}
 		f = openfont(display, name);
 		if(f == nil){
+			fprint(2, "can't open font file %s: %r\n", name);
 			warning(nil, "can't open font file %s: %r\n", name);
 			return nil;
 		}
diff --git a/src/cmd/acme/cols.c b/src/cmd/acme/cols.c
index 0e6ff40..14a715b 100644
--- a/src/cmd/acme/cols.c
+++ b/src/cmd/acme/cols.c
@@ -102,6 +102,7 @@
 		w->col = c;
 		winresize(w, r, FALSE);
 	}
+//assert(w->body.w == w);
 	w->tag.col = c;
 	w->tag.row = c->row;
 	w->body.col = c;
diff --git a/src/cmd/acme/mkfile b/src/cmd/acme/mkfile
index f92f903..cb72fea 100644
--- a/src/cmd/acme/mkfile
+++ b/src/cmd/acme/mkfile
@@ -36,6 +36,6 @@
 
 <$PLAN9/src/mkone
 
-LDFLAGS=$LDFLAGS -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 -lfmt -lutf -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 c38773e..0b9f7cc 100644
--- a/src/cmd/acme/text.c
+++ b/src/cmd/acme/text.c
@@ -8,11 +8,13 @@
 #include <frame.h>
 #include <fcall.h>
 #include <plumb.h>
+#include <complete.h>
 #include "dat.h"
 #include "fns.h"
 
 Image	*tagcols[NCOL];
 Image	*textcols[NCOL];
+static Rune Ldot[] = { '.', 0 };
 
 enum{
 	TABDIR = 3	/* width of tabs in directory windows */
@@ -526,24 +528,136 @@
 	return t->q0-q;
 }
 
+int
+textfilewidth(Text *t, uint q0, int oneelement)
+{
+	uint q;
+	Rune r;
+
+	q = q0;
+	while(q > 0){
+		r = textreadc(t, q-1);
+		if(r<=' ')
+			break;
+		if(oneelement && r=='/')
+			break;
+		--q;
+	}
+	return q0-q;
+}
+
+Rune*
+textcomplete(Text *t)
+{
+	int i, nstr, npath;
+	uint q;
+	Rune tmp[200];
+	Rune *str, *path;
+	Rune *rp;
+	Completion *c;
+	char *s, *dirs;
+	Runestr dir;
+
+	/* control-f: filename completion; works back to white space or / */
+	if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ')	/* must be at end of word */
+		return nil;
+	nstr = textfilewidth(t, t->q0, TRUE);
+	str = runemalloc(nstr);
+	npath = textfilewidth(t, t->q0-nstr, FALSE);
+	path = runemalloc(npath);
+
+	c = nil;
+	rp = nil;
+	dirs = nil;
+
+	q = t->q0-nstr;
+	for(i=0; i<nstr; i++)
+		str[i] = textreadc(t, q++);
+	q = t->q0-nstr-npath;
+	for(i=0; i<npath; i++)
+		path[i] = textreadc(t, q++);
+	/* is path rooted? if not, we need to make it relative to window path */
+	if(npath>0 && path[0]=='/')
+		dir = (Runestr){path, npath};
+	else{
+		dir = dirname(t, nil, 0);
+		if(dir.nr + 1 + npath > nelem(tmp)){
+			free(dir.r);
+			goto Return;
+		}
+		if(dir.nr == 0){
+			dir.nr = 1;
+			dir.r = runestrdup(Ldot);
+		}
+		runemove(tmp, dir.r, dir.nr);
+		tmp[dir.nr] = '/';
+		runemove(tmp+dir.nr+1, path, npath);
+		free(dir.r);
+		dir.r = tmp;
+		dir.nr += 1+npath;
+		dir = cleanrname(dir);
+	}
+
+	s = smprint("%.*S", nstr, str);
+	dirs = smprint("%.*S", dir.nr, dir.r);
+	c = complete(dirs, s);
+	free(s);
+	if(c == nil){
+		warning(nil, "error attempting completion: %r\n");
+		goto Return;
+	}
+
+	if(!c->advance){
+		warning(nil, "%.*S%s%.*S*\n",
+			dir.nr, dir.r,
+			dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
+			nstr, str);
+		for(i=0; i<c->nfile; i++)
+			warning(nil, " %s\n", c->filename[i]);
+	}
+
+	if(c->advance)
+		rp = runesmprint("%s", c->string);
+	else
+		rp = nil;
+  Return:
+	freecompletion(c);
+	free(dirs);
+	free(str);
+	free(path);
+	return rp;
+}
+
 void
 texttype(Text *t, Rune r)
 {
 	uint q0, q1;
 	int nnb, nb, n, i;
+	int nr;
+	Rune *rp;
 	Text *u;
 
 	if(t->what!=Body && r=='\n')
 		return;
+	nr = 1;
+	rp = &r;
 	switch(r){
-	case Kdown:
 	case Kleft:
+		if(t->q0 > 0)
+			textshow(t, t->q0-1, t->q0-1, TRUE);
+		return;
 	case Kright:
+		if(t->q1 < t->file->b.nc)
+			textshow(t, t->q1+1, t->q1+1, TRUE);
+		return;
+	case Kdown:
+	case Kpgdown:
 		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:
+	case Kpgup:
 		n = t->fr.maxlines/2;
 		q0 = textbacknl(t, t->org, n);
 		textsetorigin(t, q0, FALSE);
@@ -561,6 +675,13 @@
 	}
 	textshow(t, t->q0, t->q0, 1);
 	switch(r){
+	case 0x06:	/* ^F: complete */
+	case Kins:
+		rp = textcomplete(t);
+		if(rp == nil)
+			return;
+		nr = runestrlen(rp);
+		break;	/* fall through to normal insertion case */
 	case 0x1B:
 		if(t->eq0 != ~0)
 			textsetselect(t, t->eq0, t->q0);
@@ -623,16 +744,19 @@
 			u->cq0 = t->q0;
 		else if(t->q0 != u->cq0+u->ncache)
 			error("text.type cq1");
-		textinsert(u, t->q0, &r, 1, FALSE);
+		textinsert(u, t->q0, rp, nr, FALSE);
 		if(u != t)
 			textsetselect(u, u->q0, u->q1);
-		if(u->ncache == u->ncachealloc){
-			u->ncachealloc += 10;
+		if(u->ncache+nr > u->ncachealloc){
+			u->ncachealloc += 10 + nr;
 			u->cache = runerealloc(u->cache, u->ncachealloc);
 		}
-		u->cache[u->ncache++] = r;
+		runemove(u->cache+u->ncache, rp, nr);
+		u->ncache += nr;
 	}
-	textsetselect(t, t->q0+1, t->q0+1);
+	if(rp != &r)
+		free(rp);
+	textsetselect(t, t->q0+nr, t->q0+nr);
 	if(r=='\n' && t->w!=nil)
 		wincommit(t->w, t);
 }
diff --git a/src/cmd/acme/wind.c b/src/cmd/acme/wind.c
index a9a1c4b..06a815e 100644
--- a/src/cmd/acme/wind.c
+++ b/src/cmd/acme/wind.c
@@ -45,6 +45,7 @@
 		filereset(w->tag.file);
 		textsetselect(&w->tag, nc, nc);
 	}
+//assert(w->body.w == w);
 	r1 = r;
 	r1.min.y += font->height + 1;
 	if(r1.max.y < r1.min.y)
@@ -57,6 +58,7 @@
 		rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
 	}else
 		rf = rfget(FALSE, FALSE, FALSE, nil);
+//assert(w->body.w == w);
 	f = fileaddtext(f, &w->body);
 	w->body.what = Body;
 	textinit(&w->body, f, r1, rf, textcols);
@@ -72,6 +74,7 @@
 	draw(screen, br, button, nil, button->r.min);
 	w->filemenu = TRUE;
 	w->maxlines = w->body.fr.maxlines;
+//assert(w->body.w == w);
 	if(clone){
 		w->dirty = clone->dirty;
 		textsetselect(&w->body, clone->body.q0, clone->body.q1);
@@ -138,6 +141,7 @@
 	int i;
 	File *f;
 
+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);
@@ -149,6 +153,7 @@
 	int i;
 	File *f;
 
+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;