code: plan9front

Download patch

ref: b5783b1e39122f466c1a577720496284223e2217
parent: ea347ee7f181208be5a2ace0e35d268ccfefa1e4
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Mar 7 14:26:30 EST 2021

rio: make window focus changes deterministic, cleanup wind.c

Switching window focus used to be non deterministic
as the current window in focus (Window *input) was set
concurrently while processing window messages such as
Resized and Topped.

This implements a new approach where wcurrent() and
wuncurrent() are responsible for the synchronization
and switch of the input.

It is implemented by sending a Repaint message to the
old input window first, neccesarily waiting until that
window releases the focus and then input is updated
and then a Topped or Reshaped message is send to the
new input window.

Note, that when the whole screen is resized that no
input changes need to happening anymore.

--- a/sys/src/cmd/rio/dat.h
+++ b/sys/src/cmd/rio/dat.h
@@ -183,34 +183,16 @@
 void		wtopme(Window*);
 void		wbottomme(Window*);
 char*	wcontents(Window*, int*);
-int		wbswidth(Window*, Rune);
-int		wclickmatch(Window*, int, int, int, uint*);
 int		wclose(Window*);
-int		wctlmesg(Window*, int, Rectangle, void*);
 uint		wbacknl(Window*, uint, uint);
-uint		winsert(Window*, Rune*, int, uint);
-void		waddraw(Window*, Rune*, int);
-void		wborder(Window*, int);
-void		wclunk(Window*);
-void		wclosewin(Window*);
 void		wcurrent(Window*);
+void		wuncurrent(Window*);
 void		wcut(Window*);
-void		wdelete(Window*, uint, uint);
-void		wstretchsel(Window*, uint, uint*, uint*, int);
-void		wfill(Window*);
-void		wframescroll(Window*, int);
-void		wkeyctl(Window*, Rune);
-void		wmousectl(Window*);
-void		wmovemouse(Window*, Point);
 void		wpaste(Window*);
 void		wplumb(Window*);
 void		wlook(Window*);
-void		wrefresh(Window*);
-void		wrepaint(Window*);
-void		wresize(Window*, Image*);
 void		wscrdraw(Window*);
 void		wscroll(Window*, int);
-void		wselect(Window*);
 void		wsend(Window*);
 void		wsendctlmesg(Window*, int, Rectangle, void*);
 void		wsetcursor(Window*, int);
@@ -217,11 +199,9 @@
 void		wsetname(Window*);
 void		wsetorigin(Window*, uint, int);
 void		wsetpid(Window*, int, int);
-void		wsetselect(Window*, uint, uint);
 void		wshow(Window*, uint);
 void		wsnarf(Window*);
 void 		wscrsleep(Window*, uint);
-void		wsetcols(Window*, int);
 
 struct Dirtab
 {
--- a/sys/src/cmd/rio/rio.c
+++ b/sys/src/cmd/rio/rio.c
@@ -530,8 +530,10 @@
 				else
 					i = drag(winput);
 				sweeping = FALSE;
-				if(i != nil)
+				if(i != nil){
+					wcurrent(winput);
 					wsendctlmesg(winput, Reshaped, i->r, i);
+				}
 				wclose(winput);
 				continue;
 			}
@@ -616,9 +618,10 @@
 		if(j < nhidden){
 			im = allocimage(display, r, screen->chan, 0, DNofill);
 			r = ZR;
-		} else
+		} else {
 			im = allocwindow(wscreen, r, Refbackup, DNofill);
-		if(im)
+		}
+		if(im!=nil)
 			wsendctlmesg(w, Reshaped, r, im);
 		wclose(w);
 	}
@@ -1001,7 +1004,7 @@
 	Window *w;
 
 	w = pointto(TRUE);
-	if(w)
+	if(w!=nil)
 		wsendctlmesg(w, Deleted, ZR, nil);
 }
 
@@ -1016,8 +1019,10 @@
 		return;
 	incref(w);
 	i = sweep();
-	if(i)
+	if(i!=nil){
+		wcurrent(w);
 		wsendctlmesg(w, Reshaped, i->r, i);
+	}
 	wclose(w);
 }
 
@@ -1032,8 +1037,10 @@
 		return;
 	incref(w);
 	i = drag(w);
-	if(i)
+	if(i!=nil){
+		wcurrent(w);
 		wsendctlmesg(w, Reshaped, i->r, i);
+	}
 	wclose(w);
 }
 
@@ -1049,8 +1056,9 @@
 	if(nhidden >= nelem(hidden))
 		return 0;
 	incref(w);
+	wuncurrent(w);
 	i = allocimage(display, w->screenr, w->i->chan, 0, DNofill);
-	if(i){
+	if(i!=nil){
 		hidden[nhidden++] = w;
 		wsendctlmesg(w, Reshaped, ZR, i);
 	}
@@ -1070,8 +1078,9 @@
 	if(j == nhidden)
 		return -1;	/* not hidden */
 	incref(w);
+	wcurrent(w);
 	i = allocwindow(wscreen, w->i->r, Refbackup, DNofill);
-	if(i){
+	if(i!=nil){
 		--nhidden;
 		memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*));
 		wsendctlmesg(w, Reshaped, w->i->r, i);
@@ -1109,8 +1118,9 @@
 	for(j=0; j<nwindow; j++)
 		if(window[j] == w){
 			incref(w);
-			wtopme(w);
 			wcurrent(w);
+			wtopme(w);
+			wsendctlmesg(w, Topped, ZR, nil);
 			wclose(w);
 			return;
 		}
--- a/sys/src/cmd/rio/wctl.c
+++ b/sys/src/cmd/rio/wctl.c
@@ -380,6 +380,7 @@
 		} else { /* hidden */
 			if(eqrect(r, w->i->r))
 				return 1;
+			wuncurrent(w);
 			i = allocimage(display, r, w->i->chan, 0, DNofill);
 			r = ZR;
 		}
@@ -409,8 +410,9 @@
 			strcpy(err, "window is hidden");
 			return -1;
 		}
-		wtopme(w);
 		wcurrent(w);
+		wtopme(w);
+		wsendctlmesg(w, Topped, ZR, nil);
 		return 1;
 	case Hide:
 		switch(whide(w)){
--- a/sys/src/cmd/rio/wind.c
+++ b/sys/src/cmd/rio/wind.c
@@ -12,6 +12,142 @@
 #include "dat.h"
 #include "fns.h"
 
+Window*
+wlookid(int id)
+{
+	int i;
+
+	for(i=0; i<nwindow; i++)
+		if(window[i]->id == id)
+			return window[i];
+	return nil;
+}
+
+Window*
+wpointto(Point pt)
+{
+	int i;
+	Window *v, *w;
+
+	w = nil;
+	for(i=0; i<nwindow; i++){
+		v = window[i];
+		if(ptinrect(pt, v->screenr))
+		if(w==nil || v->topped>w->topped)
+			w = v;
+	}
+	return w;
+}
+
+static	int	topped;
+
+void
+wtopme(Window *w)
+{
+	if(w!=nil && w->i!=nil && w->topped!=topped){
+		w->topped = ++topped;
+		topwindow(w->i);
+		flushimage(display, 1);
+	}
+}
+
+void
+wbottomme(Window *w)
+{
+	if(w!=nil && w->i!=nil){
+		w->topped = - ++topped;
+		bottomwindow(w->i);
+		flushimage(display, 1);
+	}
+}
+
+Window*
+wtop(Point pt)
+{
+	Window *w;
+
+	w = wpointto(pt);
+	if(w!=nil){
+		incref(w);
+		wcurrent(w);
+		wtopme(w);
+		wsendctlmesg(w, Topped, ZR, nil);
+		wclose(w);
+	}
+	return w;
+}
+
+void
+wcurrent(Window *w)
+{
+	Channel *c;
+
+	if(input == nil){
+		input = w;
+		return;
+	}
+	if(w == input)
+		return;
+	incref(input);
+	c = chancreate(sizeof(Window*), 0);
+	wsendctlmesg(input, Repaint, ZR, c);
+	sendp(c, w);		/* send the new input */
+	wclose(recvp(c));	/* release old input */
+	chanfree(c);
+}
+
+void
+wuncurrent(Window *w)
+{
+	Channel *c;
+
+	if(input == nil || w != input)
+		return;
+	c = chancreate(sizeof(Window*), 0);
+	wsendctlmesg(w, Repaint, ZR, c);
+	sendp(c, nil);
+	recvp(c);
+	chanfree(c);
+}
+
+static	Cursor	*lastcursor;
+
+void
+riosetcursor(Cursor *p)
+{
+	if(p==lastcursor)
+		return;
+	setcursor(mousectl, p);
+	lastcursor = p;
+}
+
+void
+wsetcursor(Window *w, int force)
+{
+	Cursor *p;
+
+	if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
+		return;
+	if(w==nil)
+		p = nil;
+	else {
+		p = w->cursorp;
+		if(p==nil && w->holding)
+			p = &whitearrow;
+	}
+	if(p && force)	/* force cursor reload */
+		lastcursor = nil;
+	riosetcursor(p);
+}
+
+static void
+waddraw(Window *w, Rune *r, int nr)
+{
+	w->raw = runerealloc(w->raw, w->nraw+nr);
+	runemove(w->raw+w->nraw, r, nr);
+	w->nraw += nr;
+}
+
 enum
 {
 	HiWater	= 640000,	/* max size of history */
@@ -19,51 +155,177 @@
 	MinWater	= 20000,	/* room to leave available when reallocating */
 };
 
-static	int	topped;
-static	int	id;
-static	Cursor	*lastcursor;
+static uint
+winsert(Window *w, Rune *r, int n, uint q0)
+{
+	uint m;
 
-Window*
-wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+	if(n == 0)
+		return q0;
+	if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+		m = min(HiWater-LoWater, min(w->org, w->qh));
+		w->org -= m;
+		w->qh -= m;
+		if(w->q0 > m)
+			w->q0 -= m;
+		else
+			w->q0 = 0;
+		if(w->q1 > m)
+			w->q1 -= m;
+		else
+			w->q1 = 0;
+		w->nr -= m;
+		runemove(w->r, w->r+m, w->nr);
+		q0 -= m;
+	}
+	if(w->nr+n > w->maxr){
+		/*
+		 * Minimize realloc breakage:
+		 *	Allocate at least MinWater
+		 * 	Double allocation size each time
+		 *	But don't go much above HiWater
+		 */
+		m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+		if(m > HiWater)
+			m = max(HiWater+MinWater, w->nr+n);
+		if(m > w->maxr){
+			w->r = runerealloc(w->r, m);
+			w->maxr = m;
+		}
+	}
+	runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+	runemove(w->r+q0, r, n);
+	w->nr += n;
+	/* if output touches, advance selection, not qh; works best for keyboard and output */
+	if(q0 <= w->q1)
+		w->q1 += n;
+	if(q0 <= w->q0)
+		w->q0 += n;
+	if(q0 < w->qh)
+		w->qh += n;
+	if(q0 < w->org)
+		w->org += n;
+	else if(q0 <= w->org+w->nchars)
+		frinsert(w, r, r+n, q0-w->org);
+	return q0;
+}
+
+static void
+wfill(Window *w)
 {
-	Window *w;
-	Rectangle r;
+	Rune *rp;
+	int i, n, m, nl;
 
-	w = emalloc(sizeof(Window));
-	w->screenr = i->r;
-	r = insetrect(i->r, Selborder+1);
-	w->i = i;
-	w->mc = *mc;
-	w->ck = ck;
-	w->cctl = cctl;
-	w->cursorp = nil;
-	w->conswrite = chancreate(sizeof(Conswritemesg), 0);
-	w->consread =  chancreate(sizeof(Consreadmesg), 0);
-	w->kbdread =  chancreate(sizeof(Consreadmesg), 0);
-	w->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
-	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
-	w->complete = chancreate(sizeof(Completion*), 0);
-	w->gone = chancreate(sizeof(char*), 0);
-	w->scrollr = r;
-	w->scrollr.max.x = r.min.x+Scrollwid;
-	w->lastsr = ZR;
-	r.min.x += Scrollwid+Scrollgap;
-	frinit(w, r, font, i, cols);
-	w->maxtab = maxtab*stringwidth(font, "0");
-	w->topped = ++topped;
-	w->id = ++id;
-	w->notefd = -1;
-	w->scrolling = scrolling;
-	w->dir = estrdup(startdir);
-	w->label = estrdup("<unnamed>");
-	r = insetrect(w->i->r, Selborder);
-	draw(w->i, r, cols[BACK], nil, w->entire.min);
-	wborder(w, Selborder);
-	wscrdraw(w);
-	incref(w);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
-	return w;
+	while(w->lastlinefull == FALSE){
+		n = w->nr-(w->org+w->nchars);
+		if(n == 0)
+			break;
+		if(n > 2000)	/* educated guess at reasonable amount */
+			n = 2000;
+		rp = w->r+(w->org+w->nchars);
+
+		/*
+		 * it's expensive to frinsert more than we need, so
+		 * count newlines.
+		 */
+		nl = w->maxlines-w->nlines;
+		m = 0;
+		for(i=0; i<n; ){
+			if(rp[i++] == '\n'){
+				m++;
+				if(m >= nl)
+					break;
+			}
+		}
+		frinsert(w, rp, rp+i, w->nchars);
+	}
 }
 
+static void
+wsetselect(Window *w, uint q0, uint q1)
+{
+	int p0, p1;
+
+	/* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+	w->q0 = q0;
+	w->q1 = q1;
+	/* compute desired p0,p1 from q0,q1 */
+	p0 = q0-w->org;
+	p1 = q1-w->org;
+	if(p0 < 0)
+		p0 = 0;
+	if(p1 < 0)
+		p1 = 0;
+	if(p0 > w->nchars)
+		p0 = w->nchars;
+	if(p1 > w->nchars)
+		p1 = w->nchars;
+	if(p0==w->p0 && p1==w->p1)
+		return;
+	/* screen disagrees with desired selection */
+	if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+		/* no overlap or too easy to bother trying */
+		frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+		frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+		goto Return;
+	}
+	/* overlap; avoid unnecessary painting */
+	if(p0 < w->p0){
+		/* extend selection backwards */
+		frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+	}else if(p0 > w->p0){
+		/* trim first part of selection */
+		frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+	}
+	if(p1 > w->p1){
+		/* extend selection forwards */
+		frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+	}else if(p1 < w->p1){
+		/* trim last part of selection */
+		frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+	}
+
+    Return:
+	w->p0 = p0;
+	w->p1 = p1;
+}
+
+static void
+wborder(Window *w, int type)
+{
+	Image *col;
+
+	if(w->i == nil)
+		return;
+	if(w->holding){
+		if(type == Selborder)
+			col = holdcol;
+		else
+			col = paleholdcol;
+	}else{
+		if(type == Selborder)
+			col = titlecol;
+		else
+			col = lighttitlecol;
+	}
+	border(w->i, w->i->r, Selborder, col, ZP);
+}
+
+static void
+wsetcols(Window *w, int topped)
+{
+	if(w->holding)
+		if(topped)
+			w->cols[TEXT] = holdcol;
+		else
+			w->cols[TEXT] = lightholdcol;
+	else
+		if(topped)
+			w->cols[TEXT] = cols[TEXT];
+		else
+			w->cols[TEXT] = paletextcol;
+}
+
 void
 wsetname(Window *w)
 {
@@ -84,12 +346,11 @@
 	fprint(2, "rio: setname failed: %s\n", err);
 }
 
-void
+static void
 wresize(Window *w, Image *i)
 {
 	Rectangle r;
 
-	wclosewin(w);
 	w->i = i;
 	w->mc.image = i;
 	r = insetrect(i->r, Selborder+1);
@@ -99,7 +360,7 @@
 	r.min.x += Scrollwid+Scrollgap;
 	frclear(w, FALSE);
 	frinit(w, r, w->font, w->i, cols);
-	wsetcols(w, 1);
+	wsetcols(w, w == input);
 	w->maxtab = maxtab*stringwidth(w->font, "0");
 	if(!w->mouseopen || !w->winnameread){
 		r = insetrect(w->i->r, Selborder);
@@ -108,7 +369,10 @@
 		wsetselect(w, w->q0, w->q1);
 		wscrdraw(w);
 	}
-	wborder(w, Selborder);
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
 	flushimage(display, 1);
 	wsetname(w);
 	w->topped = ++topped;
@@ -118,7 +382,19 @@
 	w->wctlready = 1;
 }
 
-void
+static void
+wrepaint(Window *w)
+{
+	wsetcols(w, w == input);
+	if(!w->mouseopen || !w->winnameread)
+		frredraw(w);
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
+}
+
+static void
 wrefresh(Window *w)
 {
 	Rectangle r;
@@ -140,316 +416,11 @@
 	wscrdraw(w);
 }
 
-int
-wclose(Window *w)
-{
-	int i;
-
-	i = decref(w);
-	if(i > 0)
-		return 0;
-	if(i < 0)
-		error("negative ref count");
-	wclunk(w);
-	wsendctlmesg(w, Exited, ZR, nil);
-	return 1;
-}
-
-void
-showcandidates(Window *, Completion *);
-
-void
-winctl(void *arg)
-{
-	Rune *rp, *up, r;
-	uint qh, q0;
-	int nr, nb, c, wid, i, npart, initial, lastb;
-	char *s, *t, part[3];
-	Window *w;
-	Mousestate *mp, m;
-	enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
-	Alt alts[NWALT+1];
-	Consreadmesg crm;
-	Mousereadmesg mrm;
-	Conswritemesg cwm;
-	Stringpair pair;
-	Wctlmesg wcm;
-	Completion *cr;
-	char *kbdq[32], *kbds;
-	uint kbdqr, kbdqw;
-
-	w = arg;
-	threadsetname("winctl-id%d", w->id);
-
-	mrm.cm = chancreate(sizeof(Mouse), 0);
-	crm.c1 = chancreate(sizeof(Stringpair), 0);
-	crm.c2 = chancreate(sizeof(Stringpair), 0);
-	cwm.cw = chancreate(sizeof(Stringpair), 0);
-	
-	alts[WKbd].c = w->ck;
-	alts[WKbd].v = &kbds;
-	alts[WKbd].op = CHANRCV;
-	alts[WKbdread].c = w->kbdread;
-	alts[WKbdread].v = &crm;
-	alts[WKbdread].op = CHANSND;
-	alts[WMouse].c = w->mc.c;
-	alts[WMouse].v = &w->mc.Mouse;
-	alts[WMouse].op = CHANRCV;
-	alts[WMouseread].c = w->mouseread;
-	alts[WMouseread].v = &mrm;
-	alts[WMouseread].op = CHANSND;
-	alts[WCtl].c = w->cctl;
-	alts[WCtl].v = &wcm;
-	alts[WCtl].op = CHANRCV;
-	alts[WCwrite].c = w->conswrite;
-	alts[WCwrite].v = &cwm;
-	alts[WCwrite].op = CHANSND;
-	alts[WCread].c = w->consread;
-	alts[WCread].v = &crm;
-	alts[WCread].op = CHANSND;
-	alts[WWread].c = w->wctlread;
-	alts[WWread].v = &crm;
-	alts[WWread].op = CHANSND;
-	alts[WComplete].c = w->complete;
-	alts[WComplete].v = &cr;
-	alts[WComplete].op = CHANRCV;
-	alts[Wgone].c = w->gone;
-	alts[Wgone].v = "window deleted";
-	alts[Wgone].op = CHANNOP;
-	alts[NWALT].op = CHANEND;
-
-	kbdqr = kbdqw = 0;
-	npart = 0;
-	lastb = -1;
-	for(;;){
-		if(w->i==nil){
-			/* window deleted */
-			alts[Wgone].op = CHANSND;
-
-			alts[WKbdread].op = CHANNOP;
-			alts[WMouseread].op = CHANNOP;
-			alts[WCwrite].op = CHANNOP;
-			alts[WWread].op = CHANNOP;
-			alts[WCread].op = CHANNOP;
-		} else {
-			alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ?
-				CHANSND : CHANNOP;
-			alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? 
-				CHANSND : CHANNOP;
-			alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
-				CHANSND : CHANNOP;
-			alts[WWread].op = w->wctlready ?
-				CHANSND : CHANNOP;
-			/* this code depends on NL and EOT fitting in a single byte */
-			/* kind of expensive for each loop; worth precomputing? */
-			if(w->holding)
-				alts[WCread].op = CHANNOP;
-			else if(npart || (w->rawing && w->nraw>0))
-				alts[WCread].op = CHANSND;
-			else{
-				alts[WCread].op = CHANNOP;
-				for(i=w->qh; i<w->nr; i++){
-					c = w->r[i];
-					if(c=='\n' || c=='\004'){
-						alts[WCread].op = CHANSND;
-						break;
-					}
-				}
-			}
-		}
-		switch(alt(alts)){
-		case WKbd:
-			if(kbdqw - kbdqr < nelem(kbdq))
-				kbdq[kbdqw++ % nelem(kbdq)] = kbds;
-			else
-				free(kbds);
-			if(w->kbdopen)
-				continue;
-			while(kbdqr != kbdqw){
-				kbds = kbdq[kbdqr++ % nelem(kbdq)];
-				if(*kbds == 'c'){
-					chartorune(&r, kbds+1);
-					if(r)
-						wkeyctl(w, r);
-				}
-				free(kbds);
-			}
-			break;
-		case WKbdread:
-			recv(crm.c1, &pair);
-			nb = 0;
-			while(kbdqr != kbdqw){
-				kbds = kbdq[kbdqr % nelem(kbdq)];
-				i = strlen(kbds)+1;
-				if(nb+i > pair.ns)
-					break;
-				memmove((char*)pair.s + nb, kbds, i);
-				free(kbds);
-				nb += i;
-				kbdqr++;
-			}
-			pair.ns = nb;
-			send(crm.c2, &pair);
-			continue;
-		case WMouse:
-			if(w->mouseopen) {
-				w->mouse.counter++;
-
-				/* queue click events */
-				if(!w->mouse.qfull && lastb != w->mc.buttons) {	/* add to ring */
-					mp = &w->mouse.queue[w->mouse.wi];
-					if(++w->mouse.wi == nelem(w->mouse.queue))
-						w->mouse.wi = 0;
-					if(w->mouse.wi == w->mouse.ri)
-						w->mouse.qfull = TRUE;
-					mp->Mouse = w->mc;
-					mp->counter = w->mouse.counter;
-					lastb = w->mc.buttons;
-				}
-			} else
-				wmousectl(w);
-			break;
-		case WMouseread:
-			/* send a queued event or, if the queue is empty, the current state */
-			/* if the queue has filled, we discard all the events it contained. */
-			/* the intent is to discard frantic clicking by the user during long latencies. */
-			w->mouse.qfull = FALSE;
-			if(w->mouse.wi != w->mouse.ri) {
-				m = w->mouse.queue[w->mouse.ri];
-				if(++w->mouse.ri == nelem(w->mouse.queue))
-					w->mouse.ri = 0;
-			} else
-				m = (Mousestate){w->mc.Mouse, w->mouse.counter};
-
-			w->mouse.lastcounter = m.counter;
-			send(mrm.cm, &m.Mouse);
-			continue;
-		case WCtl:
-			if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){
-				while(kbdqr != kbdqw)
-					free(kbdq[kbdqr++ % nelem(kbdq)]);
-				chanfree(crm.c1);
-				chanfree(crm.c2);
-				chanfree(mrm.cm);
-				chanfree(cwm.cw);
-				threadexits(nil);
-			}
-			continue;
-		case WCwrite:
-			recv(cwm.cw, &pair);
-			rp = pair.s;
-			nr = pair.ns;
-			for(i=0; i<nr; i++)
-				if(rp[i] == '\b'){
-					up = rp+i;
-					initial = 0;
-					for(; i<nr; i++){
-						if(rp[i] == '\b'){
-							if(up == rp)
-								initial++;
-							else
-								up--;
-						}else
-							*up++ = rp[i];
-					}
-					if(initial){
-						if(initial > w->qh)
-							initial = w->qh;
-						qh = w->qh-initial;
-						wdelete(w, qh, qh+initial);
-						w->qh = qh;
-					}
-					nr = up - rp;
-					break;
-				}
-			w->qh = winsert(w, rp, nr, w->qh)+nr;
-			if(w->scrolling || w->mouseopen)
-				wshow(w, w->qh);
-			wsetselect(w, w->q0, w->q1);
-			wscrdraw(w);
-			free(rp);
-			break;
-		case WCread:
-			recv(crm.c1, &pair);
-			t = pair.s;
-			nb = pair.ns;
-			i = npart;
-			npart = 0;
-			if(i)
-				memmove(t, part, i);
-			while(i<nb && (w->qh<w->nr || w->nraw>0)){
-				if(w->qh == w->nr){
-					wid = runetochar(t+i, &w->raw[0]);
-					w->nraw--;
-					runemove(w->raw, w->raw+1, w->nraw);
-				}else
-					wid = runetochar(t+i, &w->r[w->qh++]);
-				c = t[i];	/* knows break characters fit in a byte */
-				i += wid;
-				if(!w->rawing && (c == '\n' || c=='\004')){
-					if(c == '\004')
-						i--;
-					break;
-				}
-			}
-			if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
-				w->qh++;
-			if(i > nb){
-				npart = i-nb;
-				memmove(part, t+nb, npart);
-				i = nb;
-			}
-			pair.s = t;
-			pair.ns = i;
-			send(crm.c2, &pair);
-			continue;
-		case WWread:
-			w->wctlready = 0;
-			recv(crm.c1, &pair);
-			s = Dx(w->screenr) > 0 ? "visible" : "hidden";
-			t = "notcurrent";
-			if(w == input)
-				t = "current";
-			pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
-				w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
-			send(crm.c2, &pair);
-			continue;
-		case WComplete:
-			if(w->i!=nil){
-				if(!cr->advance)
-					showcandidates(w, cr);
-				if(cr->advance){
-					rp = runesmprint("%s", cr->string);
-					if(rp){
-						nr = runestrlen(rp);
-						q0 = w->q0;
-						q0 = winsert(w, rp, nr, q0);
-						wshow(w, q0+nr);
-						free(rp);
-					}
-				}
-			}
-			freecompletion(cr);
-			break;
-		}
-		if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
-			flushimage(display, 1);
-	}
-}
-
-void
-waddraw(Window *w, Rune *r, int nr)
-{
-	w->raw = runerealloc(w->raw, w->nraw+nr);
-	runemove(w->raw+w->nraw, r, nr);
-	w->nraw += nr;
-}
-
 /*
  * Need to do this in a separate proc because if process we're interrupting
  * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
  */
-void
+static void
 interruptproc(void *v)
 {
 	int *notefd;
@@ -460,7 +431,35 @@
 	free(notefd);
 }
 
-int
+typedef struct Completejob Completejob;
+struct Completejob
+{
+	char	*dir;
+	char	*str;
+	Window	*win;
+};
+
+static void
+completeproc(void *arg)
+{
+	Completejob *job;
+	Completion *c;
+
+	job = arg;
+	threadsetname("namecomplete %s", job->dir);
+
+	c = complete(job->dir, job->str);
+	if(c != nil && sendp(job->win->complete, c) <= 0)
+		freecompletion(c);
+
+	wclose(job->win);
+
+	free(job->dir);
+	free(job->str);
+	free(job);
+}
+
+static int
 windfilewidth(Window *w, uint q0, int oneelement)
 {
 	uint q;
@@ -478,7 +477,45 @@
 	return q0-q;
 }
 
-void
+static void
+namecomplete(Window *w)
+{
+	int nstr, npath;
+	Rune *path, *str;
+	char *dir, *root;
+	Completejob *job;
+
+	/* control-f: filename completion; works back to white space or / */
+	if(w->q0<w->nr && w->r[w->q0]>' ')	/* must be at end of word */
+		return;
+	nstr = windfilewidth(w, w->q0, TRUE);
+	str = w->r+(w->q0-nstr);
+	npath = windfilewidth(w, w->q0-nstr, FALSE);
+	path = w->r+(w->q0-nstr-npath);
+
+	/* is path rooted? if not, we need to make it relative to window path */
+	if(npath>0 && path[0]=='/')
+		dir = runetobyte(path, npath, &npath);
+	else {
+		if(strcmp(w->dir, "") == 0)
+			root = ".";
+		else
+			root = w->dir;
+		dir = smprint("%s/%.*S", root, npath, path);
+	}
+	if(dir == nil)
+		return;
+
+	/* run in background, winctl will collect the result on w->complete chan */
+	job = emalloc(sizeof *job);
+	job->str = runetobyte(str, nstr, &nstr);
+	job->dir = cleanname(dir);
+	job->win = w;
+	incref(w);
+	proccreate(completeproc, job, STACK);
+}
+
+static void
 showcandidates(Window *w, Completion *c)
 {
 	int i;
@@ -520,73 +557,288 @@
 	free(rp);
 }
 
-typedef struct Completejob Completejob;
-struct Completejob
+static int
+wbswidth(Window *w, Rune c)
 {
-	char	*dir;
-	char	*str;
-	Window	*win;
-};
+	uint q, eq, stop;
+	Rune r;
+	int skipping;
 
+	/* there is known to be at least one character to erase */
+	if(c == 0x08)	/* ^H: erase character */
+		return 1;
+	q = w->q0;
+	stop = 0;
+	if(q > w->qh)
+		stop = w->qh;
+	skipping = TRUE;
+	while(q > stop){
+		r = w->r[q-1];
+		if(r == '\n'){		/* eat at most one more character */
+			if(q == w->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 w->q0-q;
+}
+
 void
-completeproc(void *arg)
+wsetorigin(Window *w, uint org, int exact)
 {
-	Completejob *job;
-	Completion *c;
+	int i, a, fixup;
+	Rune *r;
+	uint n;
 
-	job = arg;
-	threadsetname("namecomplete %s", job->dir);
+	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<w->nr; i++){
+			if(w->r[org] == '\n'){
+				org++;
+				break;
+			}
+			org++;
+		}
+	}
+	a = org-w->org;
+	fixup = 0;
+	if(a>=0 && a<w->nchars){
+		frdelete(w, 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<w->nchars){
+		n = w->org - org;
+		r = w->r+org;
+		frinsert(w, r, r+n, 0);
+	}else
+		frdelete(w, 0, w->nchars);
+	w->org = org;
+	wfill(w);
+	wscrdraw(w);
+	wsetselect(w, w->q0, w->q1);
+	if(fixup && w->p1 > w->p0)
+		frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
 
-	c = complete(job->dir, job->str);
-	if(c != nil && sendp(job->win->complete, c) <= 0)
-		freecompletion(c);
+uint
+wbacknl(Window *w, uint p, uint n)
+{
+	int i, j;
 
-	wclose(job->win);
+	/* look for start of this line if n==0 */
+	if(n==0 && p>0 && w->r[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(w->r[p-1]=='\n')
+				break;
+	}
+	return p;
+}
 
-	free(job->dir);
-	free(job->str);
-	free(job);
+char*
+wcontents(Window *w, int *ip)
+{
+	return runetobyte(w->r, w->nr, ip);
 }
 
 void
-namecomplete(Window *w)
+wshow(Window *w, uint q0)
 {
-	int nstr, npath;
-	Rune *path, *str;
-	char *dir, *root;
-	Completejob *job;
+	int qe;
+	int nl;
+	uint q;
 
-	/* control-f: filename completion; works back to white space or / */
-	if(w->q0<w->nr && w->r[w->q0]>' ')	/* must be at end of word */
+	qe = w->org+w->nchars;
+	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+		wscrdraw(w);
+	else{
+		nl = 4*w->maxlines/5;
+		q = wbacknl(w, q0, nl);
+		/* avoid going backwards if trying to go forwards - long lines! */
+		if(!(q0>w->org && q<w->org))
+			wsetorigin(w, q, TRUE);
+		while(q0 > w->org+w->nchars)
+			wsetorigin(w, w->org+1, FALSE);
+	}
+}
+
+void
+wsnarf(Window *w)
+{
+	if(w->q1 == w->q0)
 		return;
-	nstr = windfilewidth(w, w->q0, TRUE);
-	str = w->r+(w->q0-nstr);
-	npath = windfilewidth(w, w->q0-nstr, FALSE);
-	path = w->r+(w->q0-nstr-npath);
+	nsnarf = w->q1-w->q0;
+	snarf = runerealloc(snarf, nsnarf);
+	snarfversion++;	/* maybe modified by parent */
+	runemove(snarf, w->r+w->q0, nsnarf);
+	putsnarf();
+}
 
-	/* is path rooted? if not, we need to make it relative to window path */
-	if(npath>0 && path[0]=='/')
-		dir = runetobyte(path, npath, &npath);
-	else {
-		if(strcmp(w->dir, "") == 0)
-			root = ".";
-		else
-			root = w->dir;
-		dir = smprint("%s/%.*S", root, npath, path);
+void
+wsend(Window *w)
+{
+	getsnarf();
+	wsnarf(w);
+	if(nsnarf == 0)
+		return;
+	if(w->rawing){
+		waddraw(w, snarf, nsnarf);
+		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			waddraw(w, L"\n", 1);
+	}else{
+		winsert(w, snarf, nsnarf, w->nr);
+		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			winsert(w, L"\n", 1, w->nr);
 	}
-	if(dir == nil)
+	wsetselect(w, w->nr, w->nr);
+	wshow(w, w->nr);
+}
+
+static void
+wdelete(Window *w, uint q0, uint q1)
+{
+	uint n, p0, p1;
+
+	n = q1-q0;
+	if(n == 0)
 		return;
+	runemove(w->r+q0, w->r+q1, w->nr-q1);
+	w->nr -= n;
+	if(q0 < w->q0)
+		w->q0 -= min(n, w->q0-q0);
+	if(q0 < w->q1)
+		w->q1 -= min(n, w->q1-q0);
+	if(q1 < w->qh)
+		w->qh -= n;
+	else if(q0 < w->qh)
+		w->qh = q0;
+	if(q1 <= w->org)
+		w->org -= n;
+	else if(q0 < w->org+w->nchars){
+		p1 = q1 - w->org;
+		if(p1 > w->nchars)
+			p1 = w->nchars;
+		if(q0 < w->org){
+			w->org = q0;
+			p0 = 0;
+		}else
+			p0 = q0 - w->org;
+		frdelete(w, p0, p1);
+		wfill(w);
+	}
+}
 
-	/* run in background, winctl will collect the result on w->complete chan */
-	job = emalloc(sizeof *job);
-	job->str = runetobyte(str, nstr, &nstr);
-	job->dir = cleanname(dir);
-	job->win = w;
-	incref(w);
-	proccreate(completeproc, job, STACK);
+void
+wcut(Window *w)
+{
+	if(w->q1 == w->q0)
+		return;
+	wdelete(w, w->q0, w->q1);
+	wsetselect(w, w->q0, w->q0);
 }
 
 void
+wpaste(Window *w)
+{
+	uint q0;
+
+	if(nsnarf == 0)
+		return;
+	wcut(w);
+	q0 = w->q0;
+	if(w->rawing && q0==w->nr){
+		waddraw(w, snarf, nsnarf);
+		wsetselect(w, q0, q0);
+	}else{
+		q0 = winsert(w, snarf, nsnarf, w->q0);
+		wsetselect(w, q0, q0+nsnarf);
+	}
+}
+
+void
+wlook(Window *w)
+{
+	int i, n, e;
+
+	i = w->q1;
+	n = i - w->q0;
+	e = w->nr - n;
+	if(n <= 0 || e < n)
+		return;
+
+	if(i > e)
+		i = 0;
+
+	while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
+		if(i < e)
+			i++;
+		else
+			i = 0;
+	}
+
+	wsetselect(w, i, i+n);
+	wshow(w, i);
+}
+
+void
+wplumb(Window *w)
+{
+	Plumbmsg *m;
+	static int fd = -2;
+	char buf[32];
+	uint p0, p1;
+	Cursor *c;
+
+	if(fd == -2)
+		fd = plumbopen("send", OWRITE|OCEXEC);
+	if(fd < 0)
+		return;
+	m = emalloc(sizeof(Plumbmsg));
+	m->src = estrdup("rio");
+	m->dst = nil;
+	m->wdir = estrdup(w->dir);
+	m->type = estrdup("text");
+	p0 = w->q0;
+	p1 = w->q1;
+	if(w->q1 > w->q0)
+		m->attr = nil;
+	else{
+		while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+			p0--;
+		while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+			p1++;
+		snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+		m->attr = plumbunpackattr(buf);
+	}
+	if(p1-p0 > messagesize-1024){
+		plumbfree(m);
+		return;	/* too large for 9P */
+	}
+	m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
+	if(plumbsend(fd, m) < 0){
+		c = lastcursor;
+		riosetcursor(&query);
+		sleep(300);
+		riosetcursor(c);
+	}
+	plumbfree(m);
+}
+
+static void
 wkeyctl(Window *w, Rune r)
 {
 	uint q0 ,q1;
@@ -736,234 +988,6 @@
 	wshow(w, q0+1);
 }
 
-void
-wsetcols(Window *w, int topped)
-{
-	if(w->holding)
-		if(topped)
-			w->cols[TEXT] = holdcol;
-		else
-			w->cols[TEXT] = lightholdcol;
-	else
-		if(topped)
-			w->cols[TEXT] = cols[TEXT];
-		else
-			w->cols[TEXT] = paletextcol;
-}
-
-void
-wrepaint(Window *w)
-{
-	wsetcols(w, w == input);
-	if(!w->mouseopen || !w->winnameread)
-		frredraw(w);
-	if(w == input)
-		wborder(w, Selborder);
-	else
-		wborder(w, Unselborder);
-}
-
-int
-wbswidth(Window *w, Rune c)
-{
-	uint q, eq, stop;
-	Rune r;
-	int skipping;
-
-	/* there is known to be at least one character to erase */
-	if(c == 0x08)	/* ^H: erase character */
-		return 1;
-	q = w->q0;
-	stop = 0;
-	if(q > w->qh)
-		stop = w->qh;
-	skipping = TRUE;
-	while(q > stop){
-		r = w->r[q-1];
-		if(r == '\n'){		/* eat at most one more character */
-			if(q == w->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 w->q0-q;
-}
-
-void
-wsnarf(Window *w)
-{
-	if(w->q1 == w->q0)
-		return;
-	nsnarf = w->q1-w->q0;
-	snarf = runerealloc(snarf, nsnarf);
-	snarfversion++;	/* maybe modified by parent */
-	runemove(snarf, w->r+w->q0, nsnarf);
-	putsnarf();
-}
-
-void
-wcut(Window *w)
-{
-	if(w->q1 == w->q0)
-		return;
-	wdelete(w, w->q0, w->q1);
-	wsetselect(w, w->q0, w->q0);
-}
-
-void
-wpaste(Window *w)
-{
-	uint q0;
-
-	if(nsnarf == 0)
-		return;
-	wcut(w);
-	q0 = w->q0;
-	if(w->rawing && q0==w->nr){
-		waddraw(w, snarf, nsnarf);
-		wsetselect(w, q0, q0);
-	}else{
-		q0 = winsert(w, snarf, nsnarf, w->q0);
-		wsetselect(w, q0, q0+nsnarf);
-	}
-}
-
-void
-wplumb(Window *w)
-{
-	Plumbmsg *m;
-	static int fd = -2;
-	char buf[32];
-	uint p0, p1;
-	Cursor *c;
-
-	if(fd == -2)
-		fd = plumbopen("send", OWRITE|OCEXEC);
-	if(fd < 0)
-		return;
-	m = emalloc(sizeof(Plumbmsg));
-	m->src = estrdup("rio");
-	m->dst = nil;
-	m->wdir = estrdup(w->dir);
-	m->type = estrdup("text");
-	p0 = w->q0;
-	p1 = w->q1;
-	if(w->q1 > w->q0)
-		m->attr = nil;
-	else{
-		while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
-			p0--;
-		while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
-			p1++;
-		snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
-		m->attr = plumbunpackattr(buf);
-	}
-	if(p1-p0 > messagesize-1024){
-		plumbfree(m);
-		return;	/* too large for 9P */
-	}
-	m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
-	if(plumbsend(fd, m) < 0){
-		c = lastcursor;
-		riosetcursor(&query);
-		sleep(300);
-		riosetcursor(c);
-	}
-	plumbfree(m);
-}
-
-void
-wlook(Window *w)
-{
-	int i, n, e;
-
-	i = w->q1;
-	n = i - w->q0;
-	e = w->nr - n;
-	if(n <= 0 || e < n)
-		return;
-
-	if(i > e)
-		i = 0;
-
-	while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
-		if(i < e)
-			i++;
-		else
-			i = 0;
-	}
-
-	wsetselect(w, i, i+n);
-	wshow(w, i);
-}
-
-void
-wmousectl(Window *w)
-{
-	int but;
-
-	for(but=1;; but++){
-		if(but > 5)
-			return;
-		if(w->mc.buttons == 1<<(but-1))
-			break;
-	}
-
-	incref(w);		/* hold up window while we track */
-	if(w->i != nil){
-		if(shiftdown && but > 3)
-			wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
-		else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
-			wscroll(w, but);
-		else if(but == 1)
-			wselect(w);
-	}
-	wclose(w);
-}
-
-void
-wdelete(Window *w, uint q0, uint q1)
-{
-	uint n, p0, p1;
-
-	n = q1-q0;
-	if(n == 0)
-		return;
-	runemove(w->r+q0, w->r+q1, w->nr-q1);
-	w->nr -= n;
-	if(q0 < w->q0)
-		w->q0 -= min(n, w->q0-q0);
-	if(q0 < w->q1)
-		w->q1 -= min(n, w->q1-q0);
-	if(q1 < w->qh)
-		w->qh -= n;
-	else if(q0 < w->qh)
-		w->qh = q0;
-	if(q1 <= w->org)
-		w->org -= n;
-	else if(q0 < w->org+w->nchars){
-		p1 = q1 - w->org;
-		if(p1 > w->nchars)
-			p1 = w->nchars;
-		if(q0 < w->org){
-			w->org = q0;
-			p0 = 0;
-		}else
-			p0 = q0 - w->org;
-		frdelete(w, p0, p1);
-		wfill(w);
-	}
-}
-
-
 static Window	*clickwin;
 static uint	clickmsec;
 static Point	clickpt;
@@ -971,18 +995,7 @@
 static Window	*selectwin;
 static uint	selectq;
 
-/*
- * called from frame library
- */
-void
-framescroll(Frame *f, int dl)
-{
-	if(f != &selectwin->Frame)
-		error("frameselect not right frame");
-	wframescroll(selectwin, dl);
-}
-
-void
+static void
 wframescroll(Window *w, int dl)
 {
 	uint q0;
@@ -1009,7 +1022,118 @@
 	wsetorigin(w, q0, TRUE);
 }
 
-void
+/*
+ * called from frame library
+ */
+static void
+framescroll(Frame *f, int dl)
+{
+	if(f != &selectwin->Frame)
+		error("frameselect not right frame");
+	wframescroll(selectwin, dl);
+}
+
+static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] =  { L'\n', 0 };
+static Rune left3[] =  { L'\'', L'"', L'`', 0 };
+
+static Rune *left[] = {
+	left1,
+	left2,
+	left3,
+	nil
+};
+static Rune *right[] = {
+	right1,
+	left2,
+	left3,
+	nil
+};
+
+static int
+wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
+{
+	Rune c;
+	int nest;
+
+	nest = 1;
+	for(;;){
+		if(dir > 0){
+			if(*q == w->nr)
+				break;
+			c = w->r[*q];
+			(*q)++;
+		}else{
+			if(*q == 0)
+				break;
+			(*q)--;
+			c = w->r[*q];
+		}
+		if(c == cr){
+			if(--nest==0)
+				return 1;
+		}else if(c == cl)
+			nest++;
+	}
+	return cl=='\n' && nest==1;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+	return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+static void
+wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
+{
+	int c, i;
+	Rune *r, *l, *p;
+	uint q;
+
+	*q0 = pt;
+	*q1 = pt;
+	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 = w->r[q-1];
+		p = strrune(l, c);
+		if(p != nil){
+			if(wclickmatch(w, c, r[p-l], 1, &q))
+				*q1 = q-(c!='\n');
+			return;
+		}
+		/* try matching character to right, looking left */
+		if(q == w->nr)
+			c = '\n';
+		else
+			c = w->r[q];
+		p = strrune(r, c);
+		if(p != nil){
+			if(wclickmatch(w, c, l[p-r], -1, &q)){
+				*q1 = *q0+(*q0<w->nr && c=='\n');
+				*q0 = q;
+				if(c!='\n' || q!=0 || w->r[0]=='\n')
+					(*q0)++;
+			}
+			return;
+		}
+	}
+	/* try filling out word to right */
+	while(*q1<w->nr && inmode(w->r[*q1], mode))
+		(*q1)++;
+	/* try filling out word to left */
+	while(*q0>0 && inmode(w->r[*q0-1], mode))
+		(*q0)--;
+}
+
+static void
 wselect(Window *w)
 {
 	uint q0, q1;
@@ -1100,6 +1224,118 @@
 	}
 }
 
+/*
+ * Convert back to physical coordinates
+ */
+static void
+wmovemouse(Window *w, Point p)
+{
+	if(w != input || menuing || sweeping)
+		return;
+	p.x += w->screenr.min.x-w->i->r.min.x;
+	p.y += w->screenr.min.y-w->i->r.min.y;
+	moveto(mousectl, p);
+}
+
+
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+	static int id;
+
+	Window *w;
+	Rectangle r;
+
+	w = emalloc(sizeof(Window));
+	w->screenr = i->r;
+	r = insetrect(i->r, Selborder+1);
+	w->i = i;
+	w->mc = *mc;
+	w->ck = ck;
+	w->cctl = cctl;
+	w->cursorp = nil;
+	w->conswrite = chancreate(sizeof(Conswritemesg), 0);
+	w->consread =  chancreate(sizeof(Consreadmesg), 0);
+	w->kbdread =  chancreate(sizeof(Consreadmesg), 0);
+	w->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
+	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
+	w->complete = chancreate(sizeof(Completion*), 0);
+	w->gone = chancreate(sizeof(char*), 0);
+	w->scrollr = r;
+	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->lastsr = ZR;
+	r.min.x += Scrollwid+Scrollgap;
+	frinit(w, r, font, i, cols);
+	w->maxtab = maxtab*stringwidth(font, "0");
+	w->topped = ++topped;
+	w->id = ++id;
+	w->notefd = -1;
+	w->scrolling = scrolling;
+	w->dir = estrdup(startdir);
+	w->label = estrdup("<unnamed>");
+	r = insetrect(w->i->r, Selborder);
+	draw(w->i, r, cols[BACK], nil, w->entire.min);
+	wborder(w, Selborder);
+	wscrdraw(w);
+	incref(w);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
+	return w;
+}
+
+static void
+wclosewin(Window *w)
+{
+	Image *i = w->i;
+	if(i == nil)
+		return;
+	w->i = nil;
+	/* move it off-screen to hide it, in case client is slow in letting it go */
+	originwindow(i, i->r.min, view->r.max);
+	freeimage(i);
+}
+
+static void
+wclunk(Window *w)
+{
+	int i;
+
+	if(w->deleted)
+		return;
+	w->deleted = TRUE;
+	if(w == input){
+		input = nil;
+		riosetcursor(nil);
+	}
+	if(w == wkeyboard)
+		wkeyboard = nil;
+	for(i=0; i<nhidden; i++)
+		if(hidden[i] == w){
+			--nhidden;
+			memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
+			break;
+		}
+	for(i=0; i<nwindow; i++)
+		if(window[i] == w){
+			--nwindow;
+			memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
+			break;
+		}
+}
+
+int
+wclose(Window *w)
+{
+	int i;
+
+	i = decref(w);
+	if(i > 0)
+		return 0;
+	if(i < 0)
+		error("negative ref count");
+	wclunk(w);
+	wsendctlmesg(w, Exited, ZR, nil);
+	return 1;
+}
+
 void
 wsendctlmesg(Window *w, int type, Rectangle r, void *p)
 {
@@ -1111,7 +1347,7 @@
 	send(w->cctl, &wcm);
 }
 
-int
+static int
 wctlmesg(Window *w, int m, Rectangle r, void *p)
 {
 	Image *i = p;
@@ -1121,8 +1357,6 @@
 		error("unknown control message");
 		break;
 	case Wakeup:
-		if(p!=nil)
-			sendp((Channel*)p, w);
 		break;
 	case Reshaped:
 		if(w->deleted){
@@ -1130,54 +1364,23 @@
 			break;
 		}
 		w->screenr = r;
+		wclosewin(w);
 		wresize(w, i);
-		if(Dx(r)<=0){	/* window got hidden, if we had the input, drop it */
-			if(w==input)
-				input = nil;
-			break;
-		}
-		/* fall through to get input if needed */
+		wsetcursor(w, FALSE);
+		break;
 	case Topped:
-		if(w->deleted || w==input)
+		if(w->deleted)
 			break;
-		if(input!=nil){
-			Window *oi;
-			Channel *c;
-	
-			oi = input;
-			incref(oi);
-
-			/*
-			 * have to wait until old input responds before
-			 * changing input to us because the window might
-			 * currently be mouse tracking and it is not
-			 * prepared for getting its input revoked.
-			 */
-			c = chancreate(sizeof(void*), 0);
-			wsendctlmesg(oi, Wakeup, ZR, c);
-			recv(c, nil);
-			chanfree(c);
-
-			/*
-			 * if we are still top window and nobody else has taken
-			 * input from original window, take the input.
-			 */
-			if(!w->deleted && w->topped==topped && oi==input){
-				input = w;
-
-				oi->wctlready = 1;
-				wsendctlmesg(oi, Repaint, ZR, nil);
-			}
-			wclose(oi);
-		} else {
-			input = w;
-			wsetcursor(w, FALSE);
-		}
 		w->wctlready = 1;
-		if(m!=Topped && w==input)
-			break;
+		wsetcursor(w, FALSE);
 		/* fall thrugh for redraw after input change */
 	case Repaint:
+		if(p != nil){
+			/* sync with input change from wcurrent()/wuncurrent() */
+			Channel *c = p;
+			input = recvp(c);
+			sendp(c, w);
+		}
 		if(w->i==nil || Dx(w->screenr)<=0)
 			break;
 		wrepaint(w);
@@ -1246,178 +1449,310 @@
 	return m;
 }
 
-/*
- * Convert back to physical coordinates
- */
-void
-wmovemouse(Window *w, Point p)
+static void
+wmousectl(Window *w)
 {
-	if(w != input || menuing || sweeping)
-		return;
-	p.x += w->screenr.min.x-w->i->r.min.x;
-	p.y += w->screenr.min.y-w->i->r.min.y;
-	moveto(mousectl, p);
-}
+	int but;
 
-void
-wborder(Window *w, int type)
-{
-	Image *col;
-
-	if(w->i == nil)
-		return;
-	if(w->holding){
-		if(type == Selborder)
-			col = holdcol;
-		else
-			col = paleholdcol;
-	}else{
-		if(type == Selborder)
-			col = titlecol;
-		else
-			col = lighttitlecol;
+	for(but=1;; but++){
+		if(but > 5)
+			return;
+		if(w->mc.buttons == 1<<(but-1))
+			break;
 	}
-	border(w->i, w->i->r, Selborder, col, ZP);
-}
 
-Window*
-wpointto(Point pt)
-{
-	int i;
-	Window *v, *w;
-
-	w = nil;
-	for(i=0; i<nwindow; i++){
-		v = window[i];
-		if(ptinrect(pt, v->screenr))
-		if(w==nil || v->topped>w->topped)
-			w = v;
+	incref(w);		/* hold up window while we track */
+	if(w->i != nil){
+		if(shiftdown && but > 3)
+			wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+		else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
+			wscroll(w, but);
+		else if(but == 1)
+			wselect(w);
 	}
-	return w;
+	wclose(w);
 }
 
 void
-wcurrent(Window *w)
+winctl(void *arg)
 {
-	if(w!=nil && w!=input)
-		wsendctlmesg(w, Topped, ZR, nil);
-}
-
-void
-wsetcursor(Window *w, int force)
-{
-	Cursor *p;
-
-	if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
-		return;
-	if(w==nil)
-		p = nil;
-	else {
-		p = w->cursorp;
-		if(p==nil && w->holding)
-			p = &whitearrow;
-	}
-	if(p && force)	/* force cursor reload */
-		lastcursor = nil;
-	riosetcursor(p);
-}
-
-void
-riosetcursor(Cursor *p)
-{
-	if(p==lastcursor)
-		return;
-	setcursor(mousectl, p);
-	lastcursor = p;
-}
-
-void
-wtopme(Window *w)
-{
-	if(w!=nil && w->i!=nil && w->topped!=topped){
-		w->topped = ++topped;
-		topwindow(w->i);
-		flushimage(display, 1);
-	}
-}
-
-void
-wbottomme(Window *w)
-{
-	if(w!=nil && w->i!=nil){
-		w->topped = - ++topped;
-		bottomwindow(w->i);
-		flushimage(display, 1);
-	}
-}
-
-Window*
-wtop(Point pt)
-{
+	Rune *rp, *up, r;
+	uint qh, q0;
+	int nr, nb, c, wid, i, npart, initial, lastb;
+	char *s, *t, part[3];
 	Window *w;
+	Mousestate *mp, m;
+	enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
+	Alt alts[NWALT+1];
+	Consreadmesg crm;
+	Mousereadmesg mrm;
+	Conswritemesg cwm;
+	Stringpair pair;
+	Wctlmesg wcm;
+	Completion *cr;
+	char *kbdq[32], *kbds;
+	uint kbdqr, kbdqw;
 
-	w = wpointto(pt);
-	if(w){
-		incref(w);
-		wtopme(w);
-		wcurrent(w);
-		wclose(w);
-	}
-	return w;
-}
+	w = arg;
+	threadsetname("winctl-id%d", w->id);
 
-Window*
-wlookid(int id)
-{
-	int i;
+	mrm.cm = chancreate(sizeof(Mouse), 0);
+	crm.c1 = chancreate(sizeof(Stringpair), 0);
+	crm.c2 = chancreate(sizeof(Stringpair), 0);
+	cwm.cw = chancreate(sizeof(Stringpair), 0);
+	
+	alts[WKbd].c = w->ck;
+	alts[WKbd].v = &kbds;
+	alts[WKbd].op = CHANRCV;
+	alts[WKbdread].c = w->kbdread;
+	alts[WKbdread].v = &crm;
+	alts[WKbdread].op = CHANSND;
+	alts[WMouse].c = w->mc.c;
+	alts[WMouse].v = &w->mc.Mouse;
+	alts[WMouse].op = CHANRCV;
+	alts[WMouseread].c = w->mouseread;
+	alts[WMouseread].v = &mrm;
+	alts[WMouseread].op = CHANSND;
+	alts[WCtl].c = w->cctl;
+	alts[WCtl].v = &wcm;
+	alts[WCtl].op = CHANRCV;
+	alts[WCwrite].c = w->conswrite;
+	alts[WCwrite].v = &cwm;
+	alts[WCwrite].op = CHANSND;
+	alts[WCread].c = w->consread;
+	alts[WCread].v = &crm;
+	alts[WCread].op = CHANSND;
+	alts[WWread].c = w->wctlread;
+	alts[WWread].v = &crm;
+	alts[WWread].op = CHANSND;
+	alts[WComplete].c = w->complete;
+	alts[WComplete].v = &cr;
+	alts[WComplete].op = CHANRCV;
+	alts[Wgone].c = w->gone;
+	alts[Wgone].v = "window deleted";
+	alts[Wgone].op = CHANNOP;
+	alts[NWALT].op = CHANEND;
 
-	for(i=0; i<nwindow; i++)
-		if(window[i]->id == id)
-			return window[i];
-	return nil;
-}
+	kbdqr = kbdqw = 0;
+	npart = 0;
+	lastb = -1;
+	for(;;){
+		if(w->i==nil){
+			/* window deleted */
+			alts[Wgone].op = CHANSND;
 
-void
-wclunk(Window *w)
-{
-	int i;
+			alts[WKbdread].op = CHANNOP;
+			alts[WMouseread].op = CHANNOP;
+			alts[WCwrite].op = CHANNOP;
+			alts[WWread].op = CHANNOP;
+			alts[WCread].op = CHANNOP;
+		} else {
+			alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ?
+				CHANSND : CHANNOP;
+			alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? 
+				CHANSND : CHANNOP;
+			alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
+				CHANSND : CHANNOP;
+			alts[WWread].op = w->wctlready ?
+				CHANSND : CHANNOP;
+			/* this code depends on NL and EOT fitting in a single byte */
+			/* kind of expensive for each loop; worth precomputing? */
+			if(w->holding)
+				alts[WCread].op = CHANNOP;
+			else if(npart || (w->rawing && w->nraw>0))
+				alts[WCread].op = CHANSND;
+			else{
+				alts[WCread].op = CHANNOP;
+				for(i=w->qh; i<w->nr; i++){
+					c = w->r[i];
+					if(c=='\n' || c=='\004'){
+						alts[WCread].op = CHANSND;
+						break;
+					}
+				}
+			}
+		}
+		switch(alt(alts)){
+		case WKbd:
+			if(kbdqw - kbdqr < nelem(kbdq))
+				kbdq[kbdqw++ % nelem(kbdq)] = kbds;
+			else
+				free(kbds);
+			if(w->kbdopen)
+				continue;
+			while(kbdqr != kbdqw){
+				kbds = kbdq[kbdqr++ % nelem(kbdq)];
+				if(*kbds == 'c'){
+					chartorune(&r, kbds+1);
+					if(r)
+						wkeyctl(w, r);
+				}
+				free(kbds);
+			}
+			break;
+		case WKbdread:
+			recv(crm.c1, &pair);
+			nb = 0;
+			while(kbdqr != kbdqw){
+				kbds = kbdq[kbdqr % nelem(kbdq)];
+				i = strlen(kbds)+1;
+				if(nb+i > pair.ns)
+					break;
+				memmove((char*)pair.s + nb, kbds, i);
+				free(kbds);
+				nb += i;
+				kbdqr++;
+			}
+			pair.ns = nb;
+			send(crm.c2, &pair);
+			continue;
+		case WMouse:
+			if(w->mouseopen) {
+				w->mouse.counter++;
 
-	if(w->deleted)
-		return;
-	w->deleted = TRUE;
-	if(w == input){
-		input = nil;
-		riosetcursor(nil);
-	}
-	if(w == wkeyboard)
-		wkeyboard = nil;
-	for(i=0; i<nhidden; i++)
-		if(hidden[i] == w){
-			--nhidden;
-			memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
+				/* queue click events */
+				if(!w->mouse.qfull && lastb != w->mc.buttons) {	/* add to ring */
+					mp = &w->mouse.queue[w->mouse.wi];
+					if(++w->mouse.wi == nelem(w->mouse.queue))
+						w->mouse.wi = 0;
+					if(w->mouse.wi == w->mouse.ri)
+						w->mouse.qfull = TRUE;
+					mp->Mouse = w->mc;
+					mp->counter = w->mouse.counter;
+					lastb = w->mc.buttons;
+				}
+			} else
+				wmousectl(w);
 			break;
-		}
-	for(i=0; i<nwindow; i++)
-		if(window[i] == w){
-			--nwindow;
-			memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
+		case WMouseread:
+			/* send a queued event or, if the queue is empty, the current state */
+			/* if the queue has filled, we discard all the events it contained. */
+			/* the intent is to discard frantic clicking by the user during long latencies. */
+			w->mouse.qfull = FALSE;
+			if(w->mouse.wi != w->mouse.ri) {
+				m = w->mouse.queue[w->mouse.ri];
+				if(++w->mouse.ri == nelem(w->mouse.queue))
+					w->mouse.ri = 0;
+			} else
+				m = (Mousestate){w->mc.Mouse, w->mouse.counter};
+
+			w->mouse.lastcounter = m.counter;
+			send(mrm.cm, &m.Mouse);
+			continue;
+		case WCtl:
+			if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){
+				while(kbdqr != kbdqw)
+					free(kbdq[kbdqr++ % nelem(kbdq)]);
+				chanfree(crm.c1);
+				chanfree(crm.c2);
+				chanfree(mrm.cm);
+				chanfree(cwm.cw);
+				threadexits(nil);
+			}
+			continue;
+		case WCwrite:
+			recv(cwm.cw, &pair);
+			rp = pair.s;
+			nr = pair.ns;
+			for(i=0; i<nr; i++)
+				if(rp[i] == '\b'){
+					up = rp+i;
+					initial = 0;
+					for(; i<nr; i++){
+						if(rp[i] == '\b'){
+							if(up == rp)
+								initial++;
+							else
+								up--;
+						}else
+							*up++ = rp[i];
+					}
+					if(initial){
+						if(initial > w->qh)
+							initial = w->qh;
+						qh = w->qh-initial;
+						wdelete(w, qh, qh+initial);
+						w->qh = qh;
+					}
+					nr = up - rp;
+					break;
+				}
+			w->qh = winsert(w, rp, nr, w->qh)+nr;
+			if(w->scrolling || w->mouseopen)
+				wshow(w, w->qh);
+			wsetselect(w, w->q0, w->q1);
+			wscrdraw(w);
+			free(rp);
 			break;
+		case WCread:
+			recv(crm.c1, &pair);
+			t = pair.s;
+			nb = pair.ns;
+			i = npart;
+			npart = 0;
+			if(i)
+				memmove(t, part, i);
+			while(i<nb && (w->qh<w->nr || w->nraw>0)){
+				if(w->qh == w->nr){
+					wid = runetochar(t+i, &w->raw[0]);
+					w->nraw--;
+					runemove(w->raw, w->raw+1, w->nraw);
+				}else
+					wid = runetochar(t+i, &w->r[w->qh++]);
+				c = t[i];	/* knows break characters fit in a byte */
+				i += wid;
+				if(!w->rawing && (c == '\n' || c=='\004')){
+					if(c == '\004')
+						i--;
+					break;
+				}
+			}
+			if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
+				w->qh++;
+			if(i > nb){
+				npart = i-nb;
+				memmove(part, t+nb, npart);
+				i = nb;
+			}
+			pair.s = t;
+			pair.ns = i;
+			send(crm.c2, &pair);
+			continue;
+		case WWread:
+			w->wctlready = 0;
+			recv(crm.c1, &pair);
+			s = Dx(w->screenr) > 0 ? "visible" : "hidden";
+			t = "notcurrent";
+			if(w == input)
+				t = "current";
+			pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
+				w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+			send(crm.c2, &pair);
+			continue;
+		case WComplete:
+			if(w->i!=nil){
+				if(!cr->advance)
+					showcandidates(w, cr);
+				if(cr->advance){
+					rp = runesmprint("%s", cr->string);
+					if(rp){
+						nr = runestrlen(rp);
+						q0 = w->q0;
+						q0 = winsert(w, rp, nr, q0);
+						wshow(w, q0+nr);
+						free(rp);
+					}
+				}
+			}
+			freecompletion(cr);
+			break;
 		}
+		if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
+			flushimage(display, 1);
+	}
 }
 
 void
-wclosewin(Window *w)
-{
-	Image *i = w->i;
-	if(i == nil)
-		return;
-	w->i = nil;
-	/* move it off-screen to hide it, in case client is slow in letting it go */
-	originwindow(i, i->r.min, view->r.max);
-	freeimage(i);
-}
-
-void
 wsetpid(Window *w, int pid, int dolabel)
 {
 	char buf[32];
@@ -1480,345 +1815,4 @@
 		procexec(pidc, cmd, argv);
 		_exits("exec failed");
 	}
-}
-
-static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
-static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
-static Rune left2[] =  { L'\n', 0 };
-static Rune left3[] =  { L'\'', L'"', L'`', 0 };
-
-Rune *left[] = {
-	left1,
-	left2,
-	left3,
-	nil
-};
-Rune *right[] = {
-	right1,
-	left2,
-	left3,
-	nil
-};
-
-int
-inmode(Rune r, int mode)
-{
-	return (mode == 1) ? isalnum(r) : r && !isspace(r);
-}
-
-void
-wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
-{
-	int c, i;
-	Rune *r, *l, *p;
-	uint q;
-
-	*q0 = pt;
-	*q1 = pt;
-	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 = w->r[q-1];
-		p = strrune(l, c);
-		if(p != nil){
-			if(wclickmatch(w, c, r[p-l], 1, &q))
-				*q1 = q-(c!='\n');
-			return;
-		}
-		/* try matching character to right, looking left */
-		if(q == w->nr)
-			c = '\n';
-		else
-			c = w->r[q];
-		p = strrune(r, c);
-		if(p != nil){
-			if(wclickmatch(w, c, l[p-r], -1, &q)){
-				*q1 = *q0+(*q0<w->nr && c=='\n');
-				*q0 = q;
-				if(c!='\n' || q!=0 || w->r[0]=='\n')
-					(*q0)++;
-			}
-			return;
-		}
-	}
-	/* try filling out word to right */
-	while(*q1<w->nr && inmode(w->r[*q1], mode))
-		(*q1)++;
-	/* try filling out word to left */
-	while(*q0>0 && inmode(w->r[*q0-1], mode))
-		(*q0)--;
-}
-
-int
-wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
-{
-	Rune c;
-	int nest;
-
-	nest = 1;
-	for(;;){
-		if(dir > 0){
-			if(*q == w->nr)
-				break;
-			c = w->r[*q];
-			(*q)++;
-		}else{
-			if(*q == 0)
-				break;
-			(*q)--;
-			c = w->r[*q];
-		}
-		if(c == cr){
-			if(--nest==0)
-				return 1;
-		}else if(c == cl)
-			nest++;
-	}
-	return cl=='\n' && nest==1;
-}
-
-
-uint
-wbacknl(Window *w, uint p, uint n)
-{
-	int i, j;
-
-	/* look for start of this line if n==0 */
-	if(n==0 && p>0 && w->r[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(w->r[p-1]=='\n')
-				break;
-	}
-	return p;
-}
-
-void
-wshow(Window *w, uint q0)
-{
-	int qe;
-	int nl;
-	uint q;
-
-	qe = w->org+w->nchars;
-	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
-		wscrdraw(w);
-	else{
-		nl = 4*w->maxlines/5;
-		q = wbacknl(w, q0, nl);
-		/* avoid going backwards if trying to go forwards - long lines! */
-		if(!(q0>w->org && q<w->org))
-			wsetorigin(w, q, TRUE);
-		while(q0 > w->org+w->nchars)
-			wsetorigin(w, w->org+1, FALSE);
-	}
-}
-
-void
-wsetorigin(Window *w, 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<w->nr; i++){
-			if(w->r[org] == '\n'){
-				org++;
-				break;
-			}
-			org++;
-		}
-	}
-	a = org-w->org;
-	fixup = 0;
-	if(a>=0 && a<w->nchars){
-		frdelete(w, 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<w->nchars){
-		n = w->org - org;
-		r = w->r+org;
-		frinsert(w, r, r+n, 0);
-	}else
-		frdelete(w, 0, w->nchars);
-	w->org = org;
-	wfill(w);
-	wscrdraw(w);
-	wsetselect(w, w->q0, w->q1);
-	if(fixup && w->p1 > w->p0)
-		frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
-}
-
-void
-wsetselect(Window *w, uint q0, uint q1)
-{
-	int p0, p1;
-
-	/* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
-	w->q0 = q0;
-	w->q1 = q1;
-	/* compute desired p0,p1 from q0,q1 */
-	p0 = q0-w->org;
-	p1 = q1-w->org;
-	if(p0 < 0)
-		p0 = 0;
-	if(p1 < 0)
-		p1 = 0;
-	if(p0 > w->nchars)
-		p0 = w->nchars;
-	if(p1 > w->nchars)
-		p1 = w->nchars;
-	if(p0==w->p0 && p1==w->p1)
-		return;
-	/* screen disagrees with desired selection */
-	if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
-		/* no overlap or too easy to bother trying */
-		frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
-		frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
-		goto Return;
-	}
-	/* overlap; avoid unnecessary painting */
-	if(p0 < w->p0){
-		/* extend selection backwards */
-		frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
-	}else if(p0 > w->p0){
-		/* trim first part of selection */
-		frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
-	}
-	if(p1 > w->p1){
-		/* extend selection forwards */
-		frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
-	}else if(p1 < w->p1){
-		/* trim last part of selection */
-		frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
-	}
-
-    Return:
-	w->p0 = p0;
-	w->p1 = p1;
-}
-
-uint
-winsert(Window *w, Rune *r, int n, uint q0)
-{
-	uint m;
-
-	if(n == 0)
-		return q0;
-	if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
-		m = min(HiWater-LoWater, min(w->org, w->qh));
-		w->org -= m;
-		w->qh -= m;
-		if(w->q0 > m)
-			w->q0 -= m;
-		else
-			w->q0 = 0;
-		if(w->q1 > m)
-			w->q1 -= m;
-		else
-			w->q1 = 0;
-		w->nr -= m;
-		runemove(w->r, w->r+m, w->nr);
-		q0 -= m;
-	}
-	if(w->nr+n > w->maxr){
-		/*
-		 * Minimize realloc breakage:
-		 *	Allocate at least MinWater
-		 * 	Double allocation size each time
-		 *	But don't go much above HiWater
-		 */
-		m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
-		if(m > HiWater)
-			m = max(HiWater+MinWater, w->nr+n);
-		if(m > w->maxr){
-			w->r = runerealloc(w->r, m);
-			w->maxr = m;
-		}
-	}
-	runemove(w->r+q0+n, w->r+q0, w->nr-q0);
-	runemove(w->r+q0, r, n);
-	w->nr += n;
-	/* if output touches, advance selection, not qh; works best for keyboard and output */
-	if(q0 <= w->q1)
-		w->q1 += n;
-	if(q0 <= w->q0)
-		w->q0 += n;
-	if(q0 < w->qh)
-		w->qh += n;
-	if(q0 < w->org)
-		w->org += n;
-	else if(q0 <= w->org+w->nchars)
-		frinsert(w, r, r+n, q0-w->org);
-	return q0;
-}
-
-void
-wfill(Window *w)
-{
-	Rune *rp;
-	int i, n, m, nl;
-
-	while(w->lastlinefull == FALSE){
-		n = w->nr-(w->org+w->nchars);
-		if(n == 0)
-			break;
-		if(n > 2000)	/* educated guess at reasonable amount */
-			n = 2000;
-		rp = w->r+(w->org+w->nchars);
-
-		/*
-		 * it's expensive to frinsert more than we need, so
-		 * count newlines.
-		 */
-		nl = w->maxlines-w->nlines;
-		m = 0;
-		for(i=0; i<n; ){
-			if(rp[i++] == '\n'){
-				m++;
-				if(m >= nl)
-					break;
-			}
-		}
-		frinsert(w, rp, rp+i, w->nchars);
-	}
-}
-
-char*
-wcontents(Window *w, int *ip)
-{
-	return runetobyte(w->r, w->nr, ip);
-}
-
-void
-wsend(Window *w)
-{
-	getsnarf();
-	wsnarf(w);
-	if(nsnarf == 0)
-		return;
-	if(w->rawing){
-		waddraw(w, snarf, nsnarf);
-		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
-			waddraw(w, L"\n", 1);
-	}else{
-		winsert(w, snarf, nsnarf, w->nr);
-		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
-			winsert(w, L"\n", 1, w->nr);
-	}
-	wsetselect(w, w->nr, w->nr);
-	wshow(w, w->nr);
 }