Commit Diff


commit - a6c0ff35ee294c0a808e1792eb4f43820fed8f16
commit + e830a908498c8f0270948fd08c50f6d773315880
blob - 33104df120852310c4af127826ec67d791d6e9fc
blob + 285878e8b82b0946df372d9b1b544a98266fe938
--- src/cmd/9term/9term.c
+++ src/cmd/9term/9term.c
@@ -10,1978 +10,488 @@
 #include <frame.h>
 #include <plumb.h>
 #include <complete.h>
-#include "term.h"
-
-enum
-{
-	STACK = 32768
-};
-
-int noecho = 0;
-
-void servedevtext(void);
-void listenproc(void*);
-void textthread(void*);
-
-typedef struct Text	Text;
-typedef struct Readbuf	Readbuf;
-
-enum
-{
-	HiWater	= 640000,	/* max size of history */
-	LoWater	= 400000,	/* min size of history after max'ed */
-	MinWater	= 20000,
-};
-
-/* various geometric paramters */
-enum
-{
-	Scrollwid 	= 12,		/* width of scroll bar */
-	Scrollgap 	= 4,		/* gap right of scroll bar */
-	Maxtab		= 4,
-};
-
-enum
-{
-	Cut,
-	Paste,
-	Snarf,
-	Send,
-	Plumb,
-	Scroll,
-	Cooked,
-};
-
-#define	ESC		0x1B
-#define	CUT		0x18	/* ctrl-x */		
-#define	COPY		0x03	/* crtl-c */
-#define	PASTE		0x16	/* crtl-v */
-
-#define	READBUFSIZE 8192
-#define TRUE 1
-#define FALSE 0
-
-
-struct Text
-{
-	Frame		*f;		/* frame ofr terminal */
-	Mouse		m;
-	uint		nr;		/* num of runes in term */
-	uint		maxr;	/* max num of runes in r */
-	Rune		*r;		/* runes for term */
-	uint		nraw;		/* num of runes in raw buffer */
-	Rune		*raw;		/* raw buffer */
-	uint		org;		/* first rune on the screen */
-	uint		q0;		/* start of selection region */
-	uint		q1;		/* end of selection region */
-	uint		qh;		/* unix point */
-	int		npart;		/* partial runes read from console */
-	char		part[UTFmax];	
-	int		nsnarf;		/* snarf buffer */
-	Rune		*snarf;
-};
-
-struct Readbuf
-{
-	short	n;				/* # bytes in buf */
-	uchar	data[READBUFSIZE];		/* data bytes */
-};
-
-void	mouse(void);
-void	domenu2(int);
-void	loop(void);
-void	geom(void);
-void	fill(void);
-void	tcheck(void);
-void	updatesel(void);
-void	doreshape(void);
-void	runewrite(Rune*, int);
-void	consread(void);
-void	conswrite(char*, int);
-int	bswidth(Rune c, uint start, int eatnl);
-void	cut(void);
-void	paste(Rune*, int, int);
-void	snarfupdate(void);
-void	snarf(void);
-void	show(uint);
-void	key(Rune);
-void	setorigin(uint org, int exact);
-uint	line2q(uint);
-uint	backnl(uint, uint);
-int	cansee(uint);
-uint	backnl(uint, uint);
-void	addraw(Rune*, int);
-void	mselect(void);
-void	doubleclick(uint *q0, uint *q1);
-int	clickmatch(int cl, int cr, int dir, uint *q);
-Rune	*strrune(Rune *s, Rune c);
-int	consready(void);
-Rectangle scrpos(Rectangle r, ulong p0, ulong p1, ulong tot);
-void	scrdraw(void);
-void	scroll(int);
-void	hostproc(void *arg);
-void	hoststart(void);
-void	plumbstart(void);
-void	plumb(uint, uint);
-void	plumbclick(uint*, uint*);
-uint	insert(Rune*, int, uint, int);
-void scrolldown(int);
-void scrollup(int);
-
-#define	runemalloc(n)		malloc((n)*sizeof(Rune))
-#define	runerealloc(a, n)	realloc(a, (n)*sizeof(Rune))
-#define	runemove(a, b, n)	memmove(a, b, (n)*sizeof(Rune))
-Rectangle	scrollr;	/* scroll bar rectangle */
-Rectangle	lastsr;		/* used for scroll bar */
-int		holdon;		/* hold mode */
-int		rawon(void);		/* raw mode */
-int		cooked;		/* force cooked */
-int		scrolling;	/* window scrolls */
-int		clickmsec;	/* time of last click */
-uint		clickq0;	/* point of last click */
-int		rcfd;
-int		sfd;	/* slave fd, to get/set terminal mode */
-int		rcpid;
-int		maxtab;
-int		use9wm;
-Mousectl*	mc;
-Keyboardctl*	kc;
-Channel*	hostc;
-Readbuf		rcbuf[2];
-int		mainpid;
-int		acmecolors;
-int		plumbfd;
-int		button2exec;
-int		label(Rune*, int);
-char		wdir[1024];
-char		childwdir[1024];
-void		hangupnote(void*, char*);
-char		thesocket[100];
-
-char *menu2str[] = {
-	"cut",
-	"paste",
-	"snarf",
-	"send",
-	"plumb",
-	"scroll",
-	"cooked",
-	0
-};
-
-Image* cols[NCOL];
-Image* hcols[NCOL];
-Image* palegrey;
-Image* paleblue;
-Image* blue;
-Image *plumbcolor;
-Image *execcolor;
-
-Menu menu2 =
-{
-	menu2str
-};
-
-Text	t;
-
-Cursor whitearrow = {
-	{0, 0},
-	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
-	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
-	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
-	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
-	{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C, 
-	 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C, 
-	 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C, 
-	 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
-};
-
-Cursor query = {
-	{-7,-7},
-	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
-	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
-	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
-	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
-	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
-	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
-	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
-	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
-};
-
-void
-usage(void)
-{
-	fprint(2, "usage: 9term [-ars] [-W winsize] [cmd ...]\n");
-	threadexitsall("usage");
-}
-
-void
-threadmain(int argc, char *argv[])
-{
-	char *p, *font;
-	char buf[32];
-
-	rfork(RFNOTEG);
-	font = nil;
-	_wantfocuschanges = 1;
-	mainpid = getpid();
-	ARGBEGIN{
-	default:
-		usage();
-	case 'a':	/* acme mode */
-		button2exec++;
-		break;
-	case 'f':
-		font = EARGF(usage());
-		break;
-	case 's':
-		scrolling++;
-		break;
-	case 'w':	/* started from "rio" window manager */
-		use9wm = 1;
-		break;
-	case 'W':
-		winsize = EARGF(usage());
-		break;
-	}ARGEND
-
-	if(font)
-		putenv("font", font);
-
-	p = getenv("tabstop");
-	if(p == 0)
-		p = getenv("TABSTOP");
-	if(p != 0 && maxtab <= 0)
-		maxtab = strtoul(p, 0, 0);
-	if(maxtab <= 0)
-		maxtab = 4;	/* be like rio */
-
-	snprint(buf, sizeof buf, "%d", maxtab);
-	putenv("tabstop", buf);
-
-	initdraw(0, nil, "9term");
-	notify(hangupnote);
-	noteenable("sys: child");
-	servedevtext();
-
-	mc = initmouse(nil, screen);
-	kc = initkeyboard(nil);
-	rcpid = rcstart(argc, argv, &rcfd, &sfd);
-	hoststart();
-	plumbstart();
-
-	t.f = mallocz(sizeof(Frame), 1);
-
-	if(acmecolors){
-		cols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
-		cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkyellow);
-		cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DYellowgreen);
-	}else{
-		cols[BACK] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DWhite);
-		cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
-		cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x999999FF);
-	}
-	cols[TEXT] = display->black;
-	cols[HTEXT] = display->black;
-	palegrey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x666666FF);
-
-	hcols[BACK] = cols[BACK];
-	hcols[HIGH] = cols[HIGH];
-	blue = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DMedblue);
-	paleblue = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DGreyblue);
-
-	hcols[BORD] = blue;
-	hcols[TEXT] = hcols[BORD];
-	hcols[HTEXT] = hcols[TEXT];
-
-	plumbcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x006600FF);
-	execcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAA0000FF);
-
-	if(!blue || !palegrey || !paleblue || !plumbcolor || !execcolor)
-		sysfatal("alloc colors: %r");
-	draw(screen, screen->r, cols[BACK], nil, ZP);
-	geom();
-	loop();
-}
-
-int
-isexpand(Rune r)
-{
-	return r=='_' || ('0' <= r && r <= '9') || isalpharune(r);
-}
-
-void
-hangupnote(void *a, char *msg)
-{
-	if(getpid() != mainpid)
-		noted(NDFLT);
-	if(strcmp(msg, "hangup") == 0 && rcpid != 0){
-		postnote(PNGROUP, rcpid, "hangup");
-		noted(NDFLT);
-	}
-	if(strstr(msg, "child")){
-		char buf[128];
-		int n;
-
-		n = awaitnohang(buf, sizeof buf-1);
-		if(n > 0){
-			buf[n] = 0;
-			if(atoi(buf) == rcpid)
-				threadexitsall(0);
-		}
-		noted(NCONT);
-	}
-	noted(NDFLT);
-}
-
-void
-hostproc(void *arg)
-{
-	Channel *c;
-	int i, n, which;
-
-	c = arg;
-
-	i = 0;
-	for(;;){
-		/* Let typing have a go -- maybe there's a rubout waiting. */
-		yield();
-
-		i = 1-i;	/* toggle */
-		n = read(rcfd, rcbuf[i].data, sizeof rcbuf[i].data);
-		if(n <= 0){
-			if(n < 0)
-				fprint(2, "9term: host read error: %r\n");
-			threadexitsall("host");
-		}
-		rcbuf[i].n = n;
-		which = i;
-		send(c, &which);
-	}
-}
-
-void
-hoststart(void)
-{
-	hostc = chancreate(sizeof(int), 0);
-	proccreate(hostproc, hostc, 32*1024);
-}
-
-void
-loop(void)
-{
-	Rune r;
-	int i;
-	Alt a[5];
-
-	a[0].c = mc->c;
-	a[0].v = &mc->m;
-	a[0].op = CHANRCV;
-
-	a[1].c = kc->c;
-	a[1].v = &r;
-	a[1].op = CHANRCV;
-
-	a[2].c = hostc;
-	a[2].v = &i;
-	a[2].op = CHANRCV;
-
-	a[3].c = mc->resizec;
-	a[3].v = nil;
-	a[3].op = CHANRCV;
-
-	a[4].c = nil;
-	a[4].v = nil;
-	a[4].op = CHANEND;
-
-	for(;;) {
-		tcheck();
-
-		scrdraw();
-		flushimage(display, 1);
-		a[2].op = CHANRCV;
-		if(!scrolling && t.qh > t.org+t.f->nchars)
-			a[2].op = CHANNOP;;
-		switch(alt(a)) {
-		default:
-			sysfatal("impossible");
-		case 0:
-			t.m = mc->m;
-			mouse();
-			break;
-		case 1:
-			key(r);
-			break;
-		case 2:
-			conswrite((char*)rcbuf[i].data, rcbuf[i].n);
-			break;
-		case 3:
-			doreshape();
-			break;
-		}
-	}
-}
-
-void
-doreshape(void)
-{
-	if(getwindow(display, Refnone) < 0)
-		sysfatal("can't reattach to window");
-	draw(screen, screen->r, cols[BACK], nil, ZP);
-	geom();
-	scrdraw();
-}
-
-void
-geom(void)
-{
-	Point p;
-	Rectangle r;
-
-	if(!acmecolors){
-		if(_windowhasfocus){
-			cols[TEXT] = cols[HTEXT] = display->black;
-			hcols[TEXT] = hcols[HTEXT] = blue;
-		}else{
-			cols[TEXT] = cols[HTEXT] = palegrey;
-			hcols[TEXT] = hcols[HTEXT] = paleblue;
-		}
-	}
-
-	r = screen->r;
-	r.min.y++;
-	r.max.y--;
-
-	scrollr = r;
-	scrollr.max.x = r.min.x+Scrollwid;
-	lastsr = Rect(0,0,0,0);
-
-	r.min.x += Scrollwid+Scrollgap;
-
-	frclear(t.f, 0);
-	frinit(t.f, r, font, screen, holdon ? hcols : cols);
-	t.f->maxtab = maxtab*stringwidth(font, "0");
-	fill();
-	updatesel();
-
-	p = stringsize(font, "0");
-	if(p.x == 0 || p.y == 0)
-		return;
-
-	updatewinsize(Dy(r)/p.y, Dx(r)/p.x, Dx(r), Dy(r));
-}
-
-void
-drawhold(int holdon)
-{
-	if(holdon)
-		setcursor(mc, &whitearrow);
-	else
-		setcursor(mc, nil);
-
-	draw(screen, screen->r, cols[BACK], nil, ZP);
-	geom();
-	scrdraw();
-}
-
-void
-wordclick(uint *q0, uint *q1)
-{
-	while(*q1<t.nr && !isspace(t.r[*q1]))
-		(*q1)++;
-	while(*q0>0 && !isspace(t.r[*q0-1]))
-		(*q0)--;
-}
-
-int
-aselect(uint *q0, uint *q1, Image *color)
-{
-	int cancel;
-	uint oldq0, oldq1, newq0, newq1;
-
-	/* save old selection */
-	oldq0 = t.q0;
-	oldq1 = t.q1;
-
-	/* sweep out area and record it */
-	t.f->cols[HIGH] = color;
-	t.f->cols[HTEXT] = display->white;
-	mselect();
-	newq0 = t.q0;
-	newq1 = t.q1;
-
-	cancel = 0;
-	if(t.m.buttons != 0){
-		while(t.m.buttons){
-			readmouse(mc);
-			t.m = mc->m;
-		}
-		cancel = 1;
-	}
-
-	/* restore old selection */
-	t.f->cols[HIGH] = cols[HIGH];
-	t.f->cols[HTEXT] = cols[HTEXT];
-	t.q0 = oldq0;
-	t.q1 = oldq1;
-	updatesel();
-
-	if(cancel)
-		return -1;
-
-	/* selected a region */
-	if(newq0 < newq1){
-		*q0 = newq0;
-		*q1 = newq1;
-		return 0;
-	}
-
-	/* clicked inside previous selection */
-	/* the "<=" in newq0 <= oldq1 allows us to click the right edge */
-	if(oldq0 <= newq0 && newq0 <= oldq1){
-		*q0 = oldq0;
-		*q1 = oldq1;
-		return 0;
-	}
-
-	/* just a click */
-	*q0 = newq0;
-	*q1 = newq1;
-	return 0;
-}
-
-static Rune Lnl[1] = { '\n' };
-
-void
-mouse(void)
-{
-	int but;
-	uint q0, q1;
-
-	but = t.m.buttons;
-
-	if(but != 1 && but != 2 && but != 4 && but != 8 && but != 16)
-		return;
-
-	if (ptinrect(t.m.xy, scrollr)) {
-		scroll(but);
-		if(t.qh<=t.org+t.f->nchars)
-			consread();
-		return;
-	}
-		
-	switch(but) {
-	case 1:
-		mselect();
-		break;
-	case 2:
-		if(button2exec){
-			if(aselect(&q0, &q1, execcolor) >= 0){
-				if(q0 == q1)
-					wordclick(&q0, &q1);
-				if(q0 == q1)
-					break;
-				t.q0 = t.q1 = t.nr;
-				updatesel();
-				paste(t.r+q0, q1-q0, 1);
-				if(t.r[q1-1] != '\n')
-					paste(Lnl, 1, 1);
-			}
-			break;
-		}
-		domenu2(2);
-		break;
-	case 4:
-		bouncemouse(&t.m);
-		break;
-	/*
-		if(aselect(&q0, &q1, plumbcolor) >= 0)
-			plumb(q0, q1);
-		break;
-	*/
-	case 8:
-		scrollup(mousescrollsize(t.f->maxlines));
-		break;
-	case 16:
-		scrolldown(mousescrollsize(t.f->maxlines));
-		break;
-	}
-}
-
-void
-mselect(void)
-{
-	int b, x, y;
-	uint q0;
-
-	b = t.m.buttons;
-	q0 = frcharofpt(t.f, t.m.xy) + t.org;
-	if(t.m.msec-clickmsec<500 && clickq0==q0 && t.q0==t.q1 && b==1){
-		doubleclick(&t.q0, &t.q1);
-		updatesel();
-/*		t.t.i->flush(); */
-		x = t.m.xy.x;
-		y = t.m.xy.y;
-		/* stay here until something interesting happens */
-		do {
-			readmouse(mc);
-			t.m = mc->m;
-		} while(t.m.buttons==b && abs(t.m.xy.x-x)<4 && abs(t.m.xy.y-y)<4);
-		t.m.xy.x = x;	/* in case we're calling frselect */
-		t.m.xy.y = y;
-		clickmsec = 0;
-	}
-
-	if(t.m.buttons == b) {
-		frselect(t.f, mc);
-		t.m = mc->m;
-		t.q0 = t.f->p0 + t.org;
-		t.q1 = t.f->p1 + t.org;
-		clickmsec = t.m.msec;
-		clickq0 = t.q0;
-	}
-	if((t.m.buttons != b) &&(b&1)){
-		enum{Cancut = 1, Canpaste = 2} state = Cancut | Canpaste;
-		while(t.m.buttons){
-			if(t.m.buttons&2){
-				if(state&Cancut){
-					snarf();
-					cut();
-					state = Canpaste;
-				}
-			}else if(t.m.buttons&4){
-				if(state&Canpaste){
-					snarfupdate();
-					if(t.nsnarf){
-						paste(t.snarf, t.nsnarf, 0);
-					}
-					state = Cancut;
-				}
-			}
-			readmouse(mc);
-			t.m = mc->m;
-		}
-	}
-}
-
-Rune newline[] = { '\n', 0 };
-
-void
-domenu2(int but)
-{
-	if(scrolling)
-		menu2str[Scroll] = "+ scroll";
-	else
-		menu2str[Scroll] = "- scroll";
-	if(cooked)
-		menu2str[Cooked] = "+ mustecho";
-	else
-		menu2str[Cooked] = "- mustecho";
-
-	switch(menuhit(but, mc, &menu2, nil)){
-	case -1:
-		break;
-	case Cut:
-		snarf();
-		cut();
-		if(scrolling)
-			show(t.q0);
-		break;
-	case Paste:
-		snarfupdate();
-		paste(t.snarf, t.nsnarf, 0);
-		if(scrolling)
-			show(t.q0);
-		break;
-	case Snarf:
-		snarf();
-		if(scrolling)
-			show(t.q0);
-		break;
-	case Send:
-		if(t.q0 != t.q1)
-			snarf();
-		else
-			snarfupdate();
-		t.q0 = t.q1 = t.nr;
-		updatesel();
-		paste(t.snarf, t.nsnarf, 1);
-		if(t.nsnarf == 0 || t.snarf[t.nsnarf-1] != '\n')
-			paste(newline, 1, 1);
-		show(t.nr);
-		consread();
-		break;
-	case Scroll:
-		scrolling = !scrolling;
-		if (scrolling) {
-			show(t.nr);
-			consread();
-		}
-		break;
-	case Plumb:
-		plumb(t.q0, t.q1);
-		break;
-	case Cooked:
-		cooked = !cooked;
-		break;
-	default:
-		sysfatal("bad menu item");
-	}
-}
-
-int
-windfilewidth(uint q0, int oneelement)
-{
-	uint q;
-	Rune r;
-
-	q = q0;
-	while(q > 0){
-		r = t.r[q-1];
-		if(r<=' ')
-			break;
-		if(oneelement && r=='/')
-			break;
-		--q;
-	}
-	return q0-q;
-}
-
-void
-showcandidates(Completion *c)
-{
-	int i;
-	Fmt f;
-	Rune *rp;
-	uint nr, qline, q0;
-	char *s;
-
-	runefmtstrinit(&f);
-	if (c->nmatch == 0)
-		s = "[no matches in ";
-	else
-		s = "[";
-	if(c->nfile > 32)
-		fmtprint(&f, "%s%d files]\n", s, c->nfile);
-	else{
-		fmtprint(&f, "%s", s);
-		for(i=0; i<c->nfile; i++){
-			if(i > 0)
-				fmtprint(&f, " ");
-			fmtprint(&f, "%s", c->filename[i]);
-		}
-		fmtprint(&f, "]\n");
-	}
-	/* place text at beginning of line before host point */
-	qline = t.qh;
-	while(qline>0 && t.r[qline-1] != '\n')
-		qline--;
-
-	rp = runefmtstrflush(&f);
-	nr = runestrlen(rp);
-
-	q0 = t.q0;
-	q0 += insert(rp, nr, qline, 0) - qline;
-	free(rp);
-	t.q0 = q0+nr;
-	t.q1 = q0+nr;
-	updatesel();
-}
-
-Rune*
-namecomplete(void)
-{
-	int nstr, npath;
-	Rune *rp, *path, *str;
-	Completion *c;
-	char *s, *dir, *root;
-
-	/* control-f: filename completion; works back to white space or / */
-	if(t.q0<t.nr && t.r[t.q0]>' ')	/* must be at end of word */
-		return nil;
-	nstr = windfilewidth(t.q0, TRUE);
-	str = runemalloc(nstr);
-	runemove(str, t.r+(t.q0-nstr), nstr);
-	npath = windfilewidth(t.q0-nstr, FALSE);
-	path = runemalloc(npath);
-	runemove(path, t.r+(t.q0-nstr-npath), npath);
-	rp = nil;
-
-	/* is path rooted? if not, we need to make it relative to window path */
-	if(npath>0 && path[0]=='/'){
-		dir = malloc(UTFmax*npath+1);
-		sprint(dir, "%.*S", npath, path);
-	}else{
-		if(strcmp(wdir, "") == 0)
-			root = ".";
-		else
-			root = wdir;
-		dir = malloc(strlen(root)+1+UTFmax*npath+1);
-		sprint(dir, "%s/%.*S", root, npath, path);
-	}
-	dir = cleanname(dir);
-
-	s = smprint("%.*S", nstr, str);
-	c = complete(dir, s);
-	free(s);
-	if(c == nil)
-		goto Return;
-
-	if(!c->advance)
-		showcandidates(c);
-
-	if(c->advance)
-		rp = runesmprint("%s", c->string);
-
-  Return:
-	freecompletion(c);
-	free(dir);
-	free(path);
-	free(str);
-	return rp;
-}
-
-void
-scrollup(int n)
-{
-	setorigin(backnl(t.org, n), 1);
-}
-
-void
-scrolldown(int n)
-{
-	setorigin(line2q(n), 1);
-	if(t.qh<=t.org+t.f->nchars)
-		consread();
-}
-
-void
-key(Rune r)
-{
-	Rune *rp;
-	int nr;
-
-	if(r == 0)
-		return;
-	switch(r){
-	case Kpgup:
-		scrollup(t.f->maxlines*2/3);
-		return;
-	case Kpgdown:
-		scrolldown(t.f->maxlines*2/3);
-		return;
-	case Kup:
-		scrollup(t.f->maxlines/3);
-		return;
-	case Kdown:
-		scrolldown(t.f->maxlines/3);
-		return;
-	case Kleft:
-		if(t.q0 > 0){
-			t.q0--;
-			t.q1 = t.q0;
-			updatesel();
-			show(t.q0);
-		}
-		return;
-	case Kright:
-		if(t.q1 < t.nr){
-			t.q1++;
-			t.q0 = t.q1;
-			updatesel();
-			show(t.q1);
-		}
-		return;
-	case Khome:
-		show(0);
-		return;
-	case Kend:
-	case 0x05:
-		show(t.nr);
-		return;
-
-	/*
-	 * Non-standard extensions.
-	 */
-	case CUT:
-		snarf();
-		cut();
-		if(scrolling)
-			show(t.q0);
-		return;
-	case COPY:
-		snarf();
-		if(scrolling)
-			show(t.q0);
-		return;
-	case PASTE:
-		snarfupdate();
-		paste(t.snarf, t.nsnarf, 0);
-		if(scrolling)
-			show(t.q0);
-		return;
-	}
-
-	/*
-	 * This if used to be below the if(rawon() && t.q0==t.nr),
-	 * but let's try putting it here.  This will allow ESC-processing
-	 * to toggle hold mode even in remote SSH connections.
-	 * The drawback is that vi-style processing gets harder.
-	 * If you find yourself in some weird readline mode, good
-	 * luck getting out without ESC.  Let's see who complains.
-	 */
-	if(r==ESC){	/* toggle hold */
-		holdon = !holdon;
-		drawhold(holdon);
-	/*	replaceintegerproperty("_9WM_HOLD_MODE", 1, 32, holdon); */
-		if(!holdon)
-			consread();
-		return;
-	}
-	
-	if(!holdon && rawon() && t.q0 == t.nr){
-		addraw(&r, 1);
-		consread();
-		return;
-	}
-
-	if(r == 0x7F){	/* DEL: send interrupt; what a mess */
-		char rubout[1];
-		
-		if(holdon){
-			holdon = 0;
-			drawhold(holdon);
-		}
-		t.qh = t.q0 = t.q1 = t.nr;
-		show(t.q0);
-		rubout[0] = getintr(sfd);
-		write(rcfd, rubout, 1);
-		return;
-	}
-
-	snarf();
-
-	switch(r) {
-	case 0x06:	/* ^F: file name completion */
-	case Kins:		/* Insert: file name completion */
-		rp = namecomplete();
-		if(rp == nil)
-			return;
-		nr = runestrlen(rp);
-		paste(rp, nr, 1);
-		free(rp);
-		return;
-	case 0x08:	/* ^H: erase character */
-	case 0x15:	/* ^U: erase line */
-	case 0x17:	/* ^W: erase word */
-		if (t.q0 != 0 && t.q0 != t.qh)
-			t.q0 -= bswidth(r, t.q0, 1);
-		cut();
-		break;
-	default:
-		paste(&r, 1, 1);
-		break;
-	}
-	if(scrolling)
-		show(t.q0);
-}
-
-int
-bswidth(Rune c, uint start, int eatnl)
-{
-	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 = start;
-	stop = 0;
-	if(q > t.qh)
-		stop = t.qh;
-	skipping = 1;
-	while(q > stop){
-		r = t.r[q-1];
-		if(r == '\n'){		/* eat at most one more character */
-			if(q == start && eatnl)	/* eat the newline */
-				--q;
-			break; 
-		}
-		if(c == 0x17){
-			eq = isexpand(r);
-			if(eq && skipping)	/* found one; stop skipping */
-				skipping = 0;
-			else if(!eq && !skipping)
-				break;
-		}
-		--q;
-	}
-	return start-q;
-}
-
-int
-consready(void)
-{
-	int i, c;
-
-	if(holdon)
-		return 0;
-
-	if(rawon()) 
-		return t.nraw != 0 || t.qh < t.nr;
-
-	/* look to see if there is a complete line */
-	for(i=t.qh; i<t.nr; i++){
-		c = t.r[i];
-		if(c=='\n' || c=='\004' || c==0x7F)
-			return 1;
-	}
-	return 0;
-}
-
-
-void
-consread(void)
-{
-	char buf[8000], *p;
-	int c, width, n;
-	int s, raw;
-
-	raw = rawon();
-	for(;;) {
-		if(!consready())
-			return;
-		n = sizeof(buf);
-		p = buf;
-		c = 0;
-		while(n >= UTFmax && (t.qh<t.nr || t.nraw > 0)) {
-			if(t.qh == t.nr){
-				width = runetochar(p, &t.raw[0]);
-				t.nraw--;
-				runemove(t.raw, t.raw+1, t.nraw);
-			}else
-				width = runetochar(p, &t.r[t.qh++]);
-			c = *p;
-			p += width;
-			n -= width;
-			if(c == 0x7F){
-				*(p-1) = getintr(sfd);
-				if(!raw)
-					break;
-			}
-			if(!raw && (c == '\n' || c == '\004'))
-				break;
-		}
-		n = p-buf;
-
-		/*
-		 * If we've been echoing, make sure the terminal isn't
-		 * while we do the write.  This screws up if someone 
-		 * else tries to turn off echo at the same time we do
-		 * (we'll turn it on again after the write), but that's not
-		 * too likely.
-		 */
-		s = setecho(sfd, 0);
-		if(write(rcfd, buf, n) < 0)
-			threadexitsall(0);
-		if(s)
-			setecho(sfd, s);
-	}
-}
-
-void
-conswrite(char *p, int n)
-{
-	int n2, i;
-	Rune buf2[1000], *q;
-
-	/* convert to runes */
-	i = t.npart;
-	if(i > 0){
-		/* handle partial runes */
-		while(i < UTFmax && n>0) {
-			t.part[i] = *p;
-			i++;
-			p++;
-			n--;
-			if(fullrune(t.part, i)) {
-				t.npart = 0;
-				chartorune(buf2, t.part);
-				runewrite(buf2, 1);
-				break;
-			}
-		}
-		/* there is a little extra room in a message buf */
-	}
-
-	while(n >= UTFmax || fullrune(p, n)) {
-		n2 = nelem(buf2);
-		q = buf2;
-
-		while(n2) {
-			if(n < UTFmax && !fullrune(p, n))
-				break;
-			i = chartorune(q, p);
-			p += i;
-			n -= i;
-			n2--;
-			q++;
-		}
-		runewrite(buf2, q-buf2);
-	}
-
-	if(n != 0) {
-		assert(n+t.npart < UTFmax);
-		memcpy(t.part+t.npart, p, n);
-		t.npart += n;
-	}
-
-	if(scrolling)
-		show(t.qh);
-}
-
-void
-runewrite(Rune *r, int n)
-{
-	static int havecr;
-	int i;
-	uint initial;
-	uint q0, q1;
-	uint p0, p1;
-	Rune *p, *q;
-
-	n = label(r, n);
-	if(n == 0)
-		return;
-
-	/* process trailing \r from previous write */
-	initial = 0;
-	if(havecr && *r != '\r' && *r != '\n')
-		initial = bswidth(0x15, t.qh, 0);
-	havecr = 0;
-
-	/* get rid of backspaces */
-	p = q = r;
-	for(i=0; i<n; i++) {
-		if(*p == '\b') {
-			if(q == r)
-				initial++;
-			else
-				--q;
-		} else if(*p == '\r') {	/* treat like ^U */
-			/* convert CR without NL into erased line */
-			/* i feel really sleazy about this but it helps */
-			while(i<n-1 && *(p+1) == '\r'){
-				i++;
-				p++;
-			}
-			if(i<n-1 && *(p+1) != '\n'){
-				while(q > r && *(q-1) != '\n')
-					q--;
-				if(q==r)
-					initial = bswidth(0x15, t.qh, 0);
-			}else if(i == n-1)
-				havecr = 1;
-		} else if(*p)
-			*q++ = *p;
-		p++;
-	}
-	n = q-r;
-
-	if(initial){
-		/* write turned into a delete */
-
-		if(initial > t.qh)
-			initial = t.qh;
-		q0 = t.qh-initial;
-		q1 = t.qh;
-
-		runemove(t.r+q0, t.r+q1, t.nr-q1);
-		t.nr -= initial;
-		t.qh -= initial;
-		if(t.q0 > q1)
-			t.q0 -= initial;
-		else if(t.q0 > q0)
-			t.q0 = q0;
-		if(t.q1 > q1)
-			t.q1 -= initial;
-		else if(t.q1 > q0)
-			t.q1 = q0;
-		if(t.org > q1)
-			t.org -= initial;
-		else if(q0 < t.org+t.f->nchars){
-			if(t.org < q0)
-				p0 = q0 - t.org;
-			else {
-				t.org = q0;
-				p0 = 0;
-			}
-			p1 = q1 - t.org;
-			if(p1 > t.f->nchars)
-				p1 = t.f->nchars;
-			frdelete(t.f, p0, p1);
-			fill();
-		}
-		updatesel();
-	}
-
-	insert(r, n, t.qh, 1);
-}
-
-
-void
-cut(void)
-{
-	uint n, p0, p1;
-	uint q0, q1;
-
-	q0 = t.q0;
-	q1 = t.q1;
-
-	if (q0 < t.org && q1 >= t.org)
-		show(q0);
-
-	n = q1-q0;
-	if(n == 0)
-		return;
-	runemove(t.r+q0, t.r+q1, t.nr-q1);
-	t.nr -= n;
-	t.q0 = t.q1 = q0;
-	if(q1 < t.qh)
-		t.qh -= n;
-	else if(q0 < t.qh)
-		t.qh = q0;
-	if(q1 < t.org)
-		t.org -= n;
-	else if(q0 < t.org+t.f->nchars){
-		assert(q0 >= t.org);
-		p0 = q0 - t.org;
-		p1 = q1 - t.org;
-		if(p1 > t.f->nchars)
-			p1 = t.f->nchars;
-		frdelete(t.f, p0, p1);
-		fill();
-	}
-	updatesel();
-}
-
-void
-snarfupdate(void)
-{
-	char *pp;
-	int n, i;
-	Rune *p;
-
-	pp = getsnarf();
-	if(pp == nil)
-		return;
-	n = strlen(pp);
-	if(n <= 0) {
-		 /*t.nsnarf = 0;*/
-		return;
-	}
-	t.snarf = runerealloc(t.snarf, n);
-	for(i=0,p=t.snarf; i<n; p++)
-		i += chartorune(p, pp+i);
-	t.nsnarf = p-t.snarf;
-
-}
-
-char sbuf[SnarfSize];
-void
-snarf(void)
-{
-	char *p;
-	int i, n;
-	Rune *rp;
-
-	if(t.q1 == t.q0)
-		return;
-	n = t.q1-t.q0;
-	t.snarf = runerealloc(t.snarf, n);
-	for(i=0,p=sbuf,rp=t.snarf; i<n && p < sbuf+SnarfSize-UTFmax; i++){
-		*rp++ = *(t.r+t.q0+i);
-		p += runetochar(p, t.r+t.q0+i);
-	}
-	t.nsnarf = rp-t.snarf;
-	*p = '\0';
-	putsnarf(sbuf);
-}
-
-uint
-min(uint x, uint y)
-{
-	if(x < y)
-		return x;
-	return y;
-}
-
-uint
-max(uint x, uint y)
-{
-	if(x > y)
-		return x;
-	return y;
-}
-
-uint
-insert(Rune *r, int n, uint q0, int hostwrite)
-{
-	uint m;
-
-	if(n == 0)
-		return q0;
-	if(t.nr+n>HiWater && q0>=t.org && q0>=t.qh){
-		m = min(HiWater-LoWater, min(t.org, t.qh));
-		t.org -= m;
-		t.qh -= m;
-		if(t.q0 > m)
-			t.q0 -= m;
-		else
-			t.q0 = 0;
-		if(t.q1 > m)
-			t.q1 -= m;
-		else
-			t.q1 = 0;
-		t.nr -= m;
-		runemove(t.r, t.r+m, t.nr);
-		q0 -= m;
-	}
-	if(t.nr+n > t.maxr){
-		/*
-		 * Minimize realloc breakage:
-		 *	Allocate at least MinWater
-		 * 	Double allocation size each time
-		 *	But don't go much above HiWater
-		 */
-		m = max(min(2*(t.nr+n), HiWater), t.nr+n)+MinWater;
-		if(m > HiWater)
-			m = max(HiWater+MinWater, t.nr+n);
-		if(m > t.maxr){
-			t.r = runerealloc(t.r, m);
-			t.maxr = m;
-		}
-	}
-	runemove(t.r+q0+n, t.r+q0, t.nr-q0);
-	runemove(t.r+q0, r, n);
-	t.nr += n;
-	/* if output touches, advance selection, not qh; works best for keyboard and output */
-	if(q0 <= t.q1)
-		t.q1 += n;
-	if(q0 <= t.q0)
-		t.q0 += n;
-	if(q0 < t.qh || (q0==t.qh && hostwrite))
-		t.qh += n;
-	else
-		consread();
-	if(q0 < t.org)
-		t.org += n;
-	else if(q0 <= t.org+t.f->nchars)
-		frinsert(t.f, r, r+n, q0-t.org);
-	return q0;
-}
-
-void
-paste(Rune *r, int n, int advance)
-{
-	Rune *rbuf;
-
-	if(!holdon && rawon() && t.q0==t.nr){
-		addraw(r, n);
-		consread();
-		return;
-	}
-
-	cut();
-	if(n == 0)
-		return;
-
-	/*
-	 * if this is a button2 execute then we might have been passed
-	 * runes inside the buffer.  must save them before realloc.
-	 */
-	rbuf = nil;
-	if(t.r <= r && r < t.r+n){
-		rbuf = runemalloc(n);
-		runemove(rbuf, r, n);
-		r = rbuf;
-	}
-
-	insert(r, n, t.q0, 0);
-	updatesel();
-	free(rbuf);
-}
-
-void
-fill(void)
-{
-	if (t.f->nlines >= t.f->maxlines)
-		return;
-	frinsert(t.f, t.r + t.org + t.f->nchars, t.r + t.nr, t.f->nchars);
-}
-
-void
-updatesel(void)
-{
-	Frame *f;
-	uint n;
-
-	f = t.f;
-	if(t.org+f->p0 == t.q0 && t.org+f->p1 == t.q1)
-		return;
-
-	n = t.f->nchars;
-
-	frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
-	if (t.q0 >= t.org)
-		f->p0 = t.q0-t.org;
-	else
-		f->p0 = 0;
-	if(f->p0 > n)
-		f->p0 = n;
-	if (t.q1 >= t.org)
-		f->p1 = t.q1-t.org;
-	else
-		f->p1 = 0;
-	if(f->p1 > n)
-		f->p1 = n;
-	frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);
-
-/*
-	if(t.qh<=t.org+t.f.nchars && t.cwqueue != 0)
-		t.cwqueue->wakeup <-= 0;
-*/
-
-	tcheck();
-}
-
-void
-show(uint q0)
-{
-	int nl;
-	uint q, oq;
-
-	if(cansee(q0))
-		return;
-	
-	if (q0<t.org)
-		nl = t.f->maxlines/5;
-	else
-		nl = 4*t.f->maxlines/5;
-	q = backnl(q0, nl);
-	/* avoid going in the wrong direction */
-	if (q0>t.org && q<t.org)
-		q = t.org;
-	setorigin(q, 0);
-	/* keep trying until q0 is on the screen */
-	while(!cansee(q0)) {
-		assert(q0 >= t.org);
-		oq = q;
-		q = line2q(t.f->maxlines-nl);
-		assert(q > oq);
-		setorigin(q, 1);
-	}
-}
-
-int
-cansee(uint q0)
-{
-	uint qe;
-
-	qe = t.org+t.f->nchars;
-
-	if(q0>=t.org && q0 < qe)
-		return 1;
-	if (q0 != qe)
-		return 0;
-	if (t.f->nlines < t.f->maxlines)
-		return 1;
-	if (q0 > 0 && t.r[t.nr-1] == '\n')
-		return 0;
-	return 1;
-}
-
-
-void
-setorigin(uint org, int exact)
-{
-	int i, a;
-	uint n;
-	
-	if(org>0 && !exact){
-		/* try and start after a newline */
-		/* don't try harder than 256 chars */
-		for(i=0; i<256 && org<t.nr; i++){
-			if(t.r[org-1] == '\n')
-				break;
-			org++;
-		}
-	}
-	a = org-t.org;
-
-	if(a>=0 && a<t.f->nchars)
-		frdelete(t.f, 0, a);
-	else if(a<0 && -a<100*t.f->maxlines){
-		n = t.org - org;
-		frinsert(t.f, t.r+org, t.r+org+n, 0);
-	}else
-		frdelete(t.f, 0, t.f->nchars);
-	t.org = org;
-	fill();
-	updatesel();
-}
-
-
-uint
-line2q(uint n)
-{
-	Frame *f;
-
-	f = t.f;
-	return frcharofpt(f, Pt(f->r.min.x, f->r.min.y + n*font->height))+t.org;
-}
-
-uint
-backnl(uint p, uint n)
-{
-	int i, j;
-
-	for (i = n;; i--) {
-		/* at 256 chars, call it a line anyway */
-		for(j=256; --j>0 && p>0; p--)
-			if(t.r[p-1]=='\n')
-				break;
-		if (p == 0 || i == 0)
-			return p;
-		p--;
-	}
-}
-
-void
-addraw(Rune *r, int nr)
-{
-	t.raw = runerealloc(t.raw, t.nraw+nr);
-	runemove(t.raw+t.nraw, r, nr);
-	t.nraw += nr;
-/*
-	if(t.crqueue != nil)
-		t.crqueue->wakeup <-= 0;
-*/	
-}
-
-
-Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
-Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
-Rune left2[] =  { '\n', 0 };
-Rune left3[] =  { '\'', '"', '`', 0 };
-
-Rune *left[] = {
-	left1,
-	left2,
-	left3,
-	0
-};
-
-Rune *right[] = {
-	right1,
-	left2,
-	left3,
-	0
-};
-
-void
-doubleclick(uint *q0, uint *q1)
-{
-	int c, i;
-	Rune *r, *l, *p;
-	uint q;
-
-	for(i=0; left[i]!=0; i++){
-		q = *q0;
-		l = left[i];
-		r = right[i];
-		/* try matching character to left, looking right */
-		if(q == 0)
-			c = '\n';
-		else
-			c = t.r[q-1];
-		p = strrune(l, c);
-		if(p != 0){
-			if(clickmatch(c, r[p-l], 1, &q))
-				*q1 = q-(c!='\n');
-			return;
-		}
-		/* try matching character to right, looking left */
-		if(q == t.nr)
-			c = '\n';
-		else
-			c = t.r[q];
-		p = strrune(r, c);
-		if(p != 0){
-			if(clickmatch(c, l[p-r], -1, &q)){
-				*q1 = *q0+(*q0<t.nr && c=='\n');
-				*q0 = q;
-				if(c!='\n' || q!=0 || t.r[0]=='\n')
-					(*q0)++;
-			}
-			return;
-		}
-	}
-	/* try filling out word to right */
-	while(*q1<t.nr && isexpand(t.r[*q1]))
-		(*q1)++;
-	/* try filling out word to left */
-	while(*q0>0 && isexpand(t.r[*q0-1]))
-		(*q0)--;
-}
-
-int
-clickmatch(int cl, int cr, int dir, uint *q)
-{
-	Rune c;
-	int nest;
-
-	nest = 1;
-	for(;;){
-		if(dir > 0){
-			if(*q == t.nr)
-				break;
-			c = t.r[*q];
-			(*q)++;
-		}else{
-			if(*q == 0)
-				break;
-			(*q)--;
-			c = t.r[*q];
-		}
-		if(c == cr){
-			if(--nest==0)
-				return 1;
-		}else if(c == cl)
-			nest++;
-	}
-	return cl=='\n' && nest==1;
-}
-
-void
-tcheck(void)
-{
-	Frame *f;
-		
-	f = t.f;
-
-	assert(t.q0 <= t.q1 && t.q1 <= t.nr);
-	assert(t.org <= t.nr && t.qh <= t.nr);
-	assert(f->p0 <= f->p1 && f->p1 <= f->nchars);
-	assert(t.org + f->nchars <= t.nr);
-	assert(t.org+f->nchars==t.nr || (f->nlines >= f->maxlines));
-}
-
-Rune*
-strrune(Rune *s, Rune c)
-{
-	Rune c1;
-
-	if(c == 0) {
-		while(*s++)
-			;
-		return s-1;
-	}
-
-	while(c1 = *s++)
-		if(c1 == c)
-			return s-1;
-	return 0;
-}
-
-void
-scrdraw(void)
-{
-	Rectangle r, r1, r2;
-	static Image *scrx;
-
-	r = scrollr;
-	r.min.x += 1;	/* border between margin and bar */
-	r1 = r;
-	if(scrx==0 || scrx->r.max.y < r.max.y){
-		if(scrx)
-			freeimage(scrx);
-		scrx = allocimage(display, Rect(0, 0, 32, r.max.y), screen->chan, 1, DPaleyellow);
-		if(scrx == 0)
-			sysfatal("scroll balloc");
-	}
-	r1.min.x = 0;
-	r1.max.x = Dx(r);
-	r2 = scrpos(r1, t.org, t.org+t.f->nchars, t.nr);
-	if(!eqrect(r2, lastsr)){
-		lastsr = r2;
-		draw(scrx, r1, cols[BORD], nil, ZP);
-		draw(scrx, r2, cols[BACK], nil, r2.min);
-//		r2 = r1;
-//		r2.min.x = r2.max.x-1;
-//		draw(scrx, r2, cols[BORD], nil, ZP);
-		draw(screen, r, scrx, nil, r1.min);
-	}
-}
-
-Rectangle
-scrpos(Rectangle r, ulong p0, ulong p1, ulong tot)
-{
-	long h;
-	Rectangle q;
-
-	q = insetrect(r, 1);
-	h = q.max.y-q.min.y;
-	if(tot == 0)
-		return q;
-	if(tot > 1024L*1024L)
-		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
-scroll(int but)
-{
-	uint p0, oldp0;
-	Rectangle s;
-	int x, y, my, h, first, exact;
-
-	s = insetrect(scrollr, 1);
-	h = s.max.y-s.min.y;
-	x = (s.min.x+s.max.x)/2;
-	oldp0 = ~0;
-	first = 1;
-	do{
-		if(t.m.xy.x<s.min.x || s.max.x<=t.m.xy.x){
-			readmouse(mc);
-			t.m = mc->m;
-		}else{
-			my = t.m.xy.y;
-			if(my < s.min.y)
-				my = s.min.y;
-			if(my >= s.max.y)
-				my = s.max.y;
-//			if(!eqpt(t.m.xy, Pt(x, my)))
-//				cursorset(Pt(x, my));
-			exact = 1;
-			if(but == 2){
-				y = my;
-				if(y > s.max.y-2)
-					y = s.max.y-2;
-				if(t.nr > 1024*1024)
-					p0 = ((t.nr>>10)*(y-s.min.y)/h)<<10;
-				else
-					p0 = t.nr*(y-s.min.y)/h;
-				exact = 0;
-			} else if(but == 1)
-				p0 = backnl(t.org, (my-s.min.y)/font->height);
-			else 
-				p0 = t.org+frcharofpt(t.f, Pt(s.max.x, my));
-
-			if(oldp0 != p0)
-				setorigin(p0, exact);
-			oldp0 = p0;
-			scrdraw();
-			readmouse(mc);
-			t.m = mc->m;
-		}
-	}while(t.m.buttons & (1<<(but-1)));
-}
-
-void
-plumbstart(void)
-{
-	if((plumbfd = plumbopen("send", OWRITE)) < 0)
-		fprint(2, "9term: plumbopen: %r\n");
-}
-
-void
-plumb(uint q0, uint q1)
-{
-	Plumbmsg *pm;
-	char *p;
-	int i, p0, n;
-	char cbuf[100];
-
-	pm = malloc(sizeof(Plumbmsg));
-	pm->src = strdup("9term");
-	pm->dst = 0;
-	pm->wdir = strdup(wdir);
-	pm->type = strdup("text");
-	pm->data = nil;
-	if(q1 > q0)
-		pm->attr = nil;
-	else{
-		p0 = q0;
-		wordclick(&q0, &q1);
-		sprint(cbuf, "click=%d", p0-q0);
-		pm->attr = plumbunpackattr(cbuf);
-	}
-	if(q0==q1){
-		plumbfree(pm);
-		return;
-	}
-	pm->data = malloc(SnarfSize);
-	n = q1 - q0;
-	for(i=0,p=pm->data; i<n && p < pm->data + SnarfSize-UTFmax; i++)
-		p += runetochar(p, t.r+q0+i);
-	*p = '\0';
-	pm->ndata = strlen(pm->data);
-	if(plumbsend(plumbfd, pm) < 0){
-		setcursor(mc, &query);
-		sleep(500);
-		if(holdon)
-			setcursor(mc, &whitearrow);
-		else
-			setcursor(mc, nil);
-	}
-	plumbfree(pm);
-}
-
-/*
- * Process in-band messages about window title changes.
- * The messages are of the form:
- *
- *	\033];xxx\007
- *
- * where xxx is the new directory.  This format was chosen
- * because it changes the label on xterm windows.
- */
-int
-label(Rune *sr, int n)
-{
-	Rune *sl, *el, *er, *r;
-	char *p;
-	
-	er = sr+n;
-	for(r=er-1; r>=sr; r--)
-		if(*r == '\007')
-			break;
-	if(r < sr)
-		return n;
-
-	el = r+1;
-	if(el-sr > sizeof wdir)
-		sr = el - sizeof wdir;
-	for(sl=el-3; sl>=sr; sl--)
-		if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
-			break;
-	if(sl < sr)
-		return n;
-
-	snprint(wdir, sizeof wdir, "%.*S", (el-1)-(sl+3), sl+3);
-	drawsetlabel(wdir);
-
-	/* remove trailing /-sysname if present */
-	p = strrchr(wdir, '/');
-	if(p && *(p+1) == '-'){
-		if(p == wdir)
-			p++;
-		*p = 0;
-	}
-
-	runemove(sl, el, er-el);
-	n -= (el-sl);
-	return n;
-}
-
-int
-rawon(void)
-{
-	return !cooked && !isecho(sfd);
-}
-
-/*
- * Clumsy hack to make " and "" work.
- * Then again, what's not a clumsy hack here in Unix land?
- */
-
-char adir[100];
-int afd;
-
-void
-removethesocket(void)
-{
-	if(thesocket[0])
-		if(remove(thesocket) < 0)
-			fprint(2, "remove %s: %r\n", thesocket);
-}
-
-void
-servedevtext(void)
-{
-	char buf[100];
-
-	snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());
-
-	if((afd = announce(buf, adir)) < 0){
-		putenv("text9term", "");
-		return;
-	}
-
-	putenv("text9term", buf);
-	proccreate(listenproc, nil, STACK);
-	strcpy(thesocket, buf+5);
-	atexit(removethesocket);
-}
-
-void
-listenproc(void *arg)
-{
-	int fd;
-	char dir[100];
-
-	USED(arg);
-	for(;;){
-		fd = listen(adir, dir);
-		if(fd < 0){
-			close(afd);
-			return;
-		}
-		proccreate(textthread, (void*)fd, STACK);
-	}
-}
-
-void
-textthread(void *arg)
-{
-	int fd, i, x, n, end;
-	Rune r;
-	char buf[4096], *p, *ep;
-
-	fd = (int)arg;
-	p = buf;
-	ep = buf+sizeof buf;
-	end = t.org+t.nr;	/* avoid possible output loop */
-	for(i=t.org;; i++){
-		if(i >= end || ep-p < UTFmax){
-			for(x=0; x<p-buf; x+=n)
-				if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
-					goto break2;
-			
-			if(i >= end)
-				break;
-			p = buf;
-		}
-		if(i < t.org)
-			i = t.org;
-		r = t.r[i-t.org];
-		if(r < Runeself)
-			*p++ = r;
-		else
-			p += runetochar(p, &r);
-	}
-break2:
-	close(fd);
-}
+#define Extern
+#include "dat.h"
+#include "fns.h"
+#include "term.h"
+
+int use9wm;
+int mainpid;
+int plumbfd;
+int rcpid;
+int rcfd;
+int sfd;
+int noecho;
+Window *w;
+char *fontname;
+
+void derror(Display*, char*);
+void	mousethread(void*);
+void	keyboardthread(void*);
+void winclosethread(void*);
+void deletethread(void*);
+void rcoutputproc(void*);
+void	rcinputproc(void*);
+void hangupnote(void*, char*);
+void resizethread(void*);
+
+int errorshouldabort = 0;
+
+void
+usage(void)
+{
+	fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n");
+	threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	char *p, *font;
+	
+	rfork(RFNOTEG);
+	font = nil;
+	_wantfocuschanges = 1;
+	mainpid = getpid();
+	messagesize = 8192;
+	
+	ARGBEGIN{
+	default:
+		usage();
+	case 'f':
+		font = EARGF(usage());
+		break;
+	case 's':
+		scrolling = TRUE;
+		break;
+	case 'w':	/* started from rio or 9wm */
+		use9wm = TRUE;
+		break;
+	case 'W':
+		winsize = EARGF(usage());
+		break;
+	}ARGEND
+	
+	if(font)
+		putenv("font", font);
+	
+	p = getenv("tabstop");
+	if(p == 0)
+		p = getenv("TABSTOP");
+	if(p && maxtab <= 0)
+		maxtab = strtoul(p, 0, 0);
+	if(maxtab <= 0)
+		maxtab = 4;
+	free(p);
+	
+	startdir = ".";
+	
+	initdraw(derror, nil, "9term");
+	notify(hangupnote);
+	noteenable("sys: child");
+//	servedevtext();
+	
+	mousectl = initmouse(nil, screen);
+	if(mousectl == nil)
+		error("cannot find mouse");
+	keyboardctl = initkeyboard(nil);
+	if(keyboardctl == nil)
+		error("cannot find keyboard");
+	if((plumbfd = plumbopen("send", OWRITE)) < 0)
+		fprint(2, "9term: plumbopen: %r\n");
+	mouse = &mousectl->m;
+
+	winclosechan = chancreate(sizeof(Window*), 0);
+	deletechan = chancreate(sizeof(char*), 0);
+
+	timerinit();
+	rcpid = rcstart(argc, argv, &rcfd, &sfd);
+	w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil);
+
+	threadcreate(keyboardthread, nil, STACK);
+	threadcreate(mousethread, nil, STACK);
+	threadcreate(resizethread, nil, STACK);
+	
+	proccreate(rcoutputproc, nil, STACK);
+	proccreate(rcinputproc, nil, STACK);
+}
+
+void
+derror(Display *d, char *errorstr)
+{
+	USED(d);
+	error(errorstr);
+}
+
+void
+hangupnote(void *a, char *msg)
+{
+	if(getpid() != mainpid)
+		noted(NDFLT);
+	if(strcmp(msg, "hangup") == 0 && rcpid != 0){
+		postnote(PNGROUP, rcpid, "hangup");
+		noted(NDFLT);
+	}
+	if(strstr(msg, "child")){
+		char buf[128];
+		int n;
+
+		n = awaitnohang(buf, sizeof buf-1);
+		if(n > 0){
+			buf[n] = 0;
+			if(atoi(buf) == rcpid)
+				threadexitsall(0);
+		}
+		noted(NCONT);
+	}
+	noted(NDFLT);
+}
+
+void
+keyboardthread(void *v)
+{
+	Rune buf[2][20], *rp;
+	int i, n;
+
+	USED(v);
+	threadsetname("keyboardthread");
+	n = 0;
+	for(;;){
+		rp = buf[n];
+		n = 1-n;
+		recv(keyboardctl->c, rp);
+		for(i=1; i<nelem(buf[0])-1; i++)
+			if(nbrecv(keyboardctl->c, rp+i) <= 0)
+				break;
+		rp[i] = L'\0';
+		sendp(w->ck, rp);
+	}
+}
+
+void
+resizethread(void *v)
+{
+	USED(v);
+	
+	while(recv(mousectl->resizec, nil) == 1){
+		if(getwindow(display, Refnone) < 0)
+			sysfatal("can't reattach to window");
+		wresize(w, screen, 0);
+	}
+}
+			
+void
+mousethread(void *v)
+{
+	int sending;
+	Mouse tmp;
+
+	USED(v);
+
+	sending = FALSE;
+	threadsetname("mousethread");
+	while(readmouse(mousectl) >= 0){
+		if(sending){
+		Send:
+			/* send to window */
+			if(mouse->buttons == 0)
+				sending = FALSE;
+			else
+				wsetcursor(w, 0);
+			tmp = mousectl->m;
+			send(w->mc.c, &tmp);
+			continue;
+		}
+		if(mouse->buttons&1){
+			sending = TRUE;
+			goto Send;
+		}else if(mouse->buttons&2)
+			button2menu(w);
+		else
+			/* send to rio */;
+	}
+}
+		
+void
+wborder(Window *w, int type)
+{
+}
+
+Window*
+wpointto(Point pt)
+{
+	return w;
+}
+
+Window*
+new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
+{
+	Window *w;
+	Mousectl *mc;
+	Channel *cm, *ck, *cctl;
+
+	if(i == nil)
+		return nil;
+	cm = chancreate(sizeof(Mouse), 0);
+	ck = chancreate(sizeof(Rune*), 0);
+	cctl = chancreate(sizeof(Wctlmesg), 4);
+	if(cm==nil || ck==nil || cctl==nil)
+		error("new: channel alloc failed");
+	mc = emalloc(sizeof(Mousectl));
+	*mc = *mousectl;
+//	mc->image = i;
+	mc->c = cm;
+	w = wmk(i, mc, ck, cctl, scrollit);
+	free(mc);	/* wmk copies *mc */
+	window = erealloc(window, ++nwindow*sizeof(Window*));
+	window[nwindow-1] = w;
+	if(hideit){
+		hidden[nhidden++] = w;
+		w->screenr = ZR;
+	}
+	threadcreate(winctl, w, 8192);
+	if(!hideit)
+		wcurrent(w);
+	flushimage(display, 1);
+	wsetpid(w, pid, 1);
+	wsetname(w);
+	if(dir)
+		w->dir = estrdup(dir);
+	return w;
+}
+
+/*
+ * Button 2 menu.  Extra entry for always cook
+ */
+int cooked;
+
+enum
+{
+	Cut,
+	Paste,
+	Snarf,
+	Plumb,
+	Send,
+	Scroll,
+	Cook,
+};
+
+char		*menu2str[] = {
+	"cut",
+	"paste",
+	"snarf",
+	"plumb",
+	"send",
+	"scroll",
+	"cook",
+	nil
+};
+
+
+Menu menu2 =
+{
+	menu2str
+};
+
+Rune newline[] = { '\n' };
+
+void
+button2menu(Window *w)
+{
+	if(w->deleted)
+		return;
+	incref(&w->ref);
+	if(w->scrolling)
+		menu2str[Scroll] = "noscroll";
+	else
+		menu2str[Scroll] = "scroll";
+	if(cooked)
+		menu2str[Cook] = "nocook";
+	else
+		menu2str[Cook] = "cook";
+
+	switch(menuhit(2, mousectl, &menu2, wscreen)){
+	case Cut:
+		wsnarf(w);
+		wcut(w);
+		wscrdraw(w);
+		break;
+
+	case Snarf:
+		wsnarf(w);
+		break;
+
+	case Paste:
+	//XXX	getsnarf();
+		wpaste(w);
+		wscrdraw(w);
+		break;
+
+	case Plumb:
+		wplumb(w);
+		break;
+
+	case Send:
+	//XXX	getsnarf();
+		wsnarf(w);
+		if(nsnarf == 0)
+			break;
+		if(w->rawing){
+			waddraw(w, snarf, nsnarf);
+			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+				waddraw(w, newline, 1);
+		}else{
+			winsert(w, snarf, nsnarf, w->nr);
+			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+				winsert(w, newline, 1, w->nr);
+		}
+		wsetselect(w, w->nr, w->nr);
+		wshow(w, w->nr);
+		break;
+
+	case Scroll:
+		if(w->scrolling ^= 1)
+			wshow(w, w->nr);
+		break;
+	
+	case Cook:
+		cooked ^= 1;
+		break;
+	}
+	wclose(w);
+	wsendctlmesg(w, Wakeup, ZR, nil);
+	flushimage(display, 1);
+}
+
+int
+rawon(void)
+{
+	return !cooked && !isecho(sfd);
+}
+
+/*
+ * I/O with child rc.
+ */
+
+int label(Rune*, int);
+
+void
+rcoutputproc(void *arg)
+{
+	int i, cnt, n, nb, nr;
+	static char data[9000];
+	Conswritemesg cwm;
+	Rune *r;
+	Stringpair pair;
+	
+	i = 0;
+	cnt = 0;
+	for(;;){
+		/* XXX Let typing have a go -- maybe there's a rubout waiting. */
+		i = 1-i;
+		n = read(rcfd, data+cnt, sizeof data-cnt);
+		if(n <= 0){
+			if(n < 0)
+				fprint(2, "9term: rc read error: %r\n");
+			threadexitsall("eof on rc output");
+		}
+		cnt += n;
+		r = runemalloc(cnt);
+		cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
+		/* approach end of buffer */
+		while(fullrune(data+nb, cnt-nb)){
+			nb += chartorune(&r[nr], data+nb);
+			if(r[nr])
+				nr++;
+		}
+		if(nb < cnt)
+			memmove(data, data+nb, cnt-nb);
+		cnt -= nb;
+		
+		nr = label(r, nr);
+		if(nr == 0)
+			continue;
+
+		recv(w->conswrite, &cwm);
+		pair.s = r;
+		pair.ns = nr;
+		send(cwm.cw, &pair);
+	}
+}
+
+/*
+ * Process in-band messages about window title changes.
+ * The messages are of the form:
+ *
+ *	\033];xxx\007
+ *
+ * where xxx is the new directory.  This format was chosen
+ * because it changes the label on xterm windows.
+ */
+int
+label(Rune *sr, int n)
+{
+	Rune *sl, *el, *er, *r;
+	char *p, *dir;
+	
+	er = sr+n;
+	for(r=er-1; r>=sr; r--)
+		if(*r == '\007')
+			break;
+	if(r < sr)
+		return n;
+
+	el = r+1;
+	for(sl=el-3; sl>=sr; sl--)
+		if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
+			break;
+	if(sl < sr)
+		return n;
+
+	dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
+	if(dir){
+		drawsetlabel(dir);
+		free(w->dir);
+		w->dir = dir;
+	}
+
+	/* remove trailing /-sysname if present */
+	p = strrchr(dir, '/');
+	if(p && *(p+1) == '-'){
+		if(p == dir)
+			p++;
+		*p = 0;
+	}
+
+	runemove(sl, el, er-el);
+	n -= (el-sl);
+	return n;
+}
+
+void
+rcinputproc(void *arg)
+{
+	static char data[9000];
+	int s;
+	Consreadmesg crm;
+	Channel *c1, *c2;
+	Stringpair pair;
+
+	for(;;){
+		recv(w->consread, &crm);
+		c1 = crm.c1;
+		c2 = crm.c2;
+		
+		pair.s = data;
+		pair.ns = sizeof data;
+		send(c1, &pair);
+		recv(c2, &pair);
+		
+		s = setecho(sfd, 0);
+		if(write(rcfd, pair.s, pair.ns) < 0)
+			threadexitsall(nil);
+		if(s)
+			setecho(sfd, s);
+	}
+}
+
blob - ae2618f4de10e27dd7dd96f0ba9bcc9cb8a4b2be
blob + e1ef4d680593f83d84b10ee5a74944af39eb6748
--- src/cmd/9term/mkfile
+++ src/cmd/9term/mkfile
@@ -6,7 +6,11 @@ OFILES=\
 	rcstart.$O\
 	$SYSNAME.$O\
 
+HFILES=dat.h fns.h term.h
+
 <$PLAN9/src/mkmany
 
 Darwin.$O Linux.$O FreeBSD.$O: bsdpty.c
 
+$O.9term: data.$O scrl.$O time.$O util.$O wind.$O
+
blob - /dev/null
blob + cf7aa9f9c461c92463404cd7192d4c953541f139 (mode 644)
--- /dev/null
+++ src/cmd/9term/dat.h
@@ -0,0 +1,243 @@
+#define STACK 32768
+#undef Borderwidth
+#define Borderwidth 0
+
+typedef	struct	Consreadmesg Consreadmesg;
+typedef	struct	Conswritemesg Conswritemesg;
+typedef	struct	Stringpair Stringpair;
+typedef	struct	Dirtab Dirtab;
+typedef	struct	Mouseinfo	Mouseinfo;
+typedef	struct	Mousereadmesg Mousereadmesg;
+typedef	struct	Mousestate	Mousestate;
+typedef	struct	Timer Timer;
+typedef	struct	Wctlmesg Wctlmesg;
+typedef	struct	Window Window;
+
+enum
+{
+	Selborder		= 0,		/* border of selected window */
+	Unselborder	= 0,		/* border of unselected window */
+	Scrollwid 		= 12,		/* width of scroll bar */
+	Scrollgap 		= 4,		/* gap right of scroll bar */
+	BIG			= 3,		/* factor by which window dimension can exceed screen */
+	TRUE		= 1,
+	FALSE		= 0,
+};
+
+enum
+{
+	Kscrolloneup = KF|0x20,
+	Kscrollonedown = KF|0x21,
+};
+
+enum	/* control messages */
+{
+	Wakeup,
+	Reshaped,
+	Moved,
+	Refresh,
+	Movemouse,
+	Rawon,
+	Rawoff,
+	Holdon,
+	Holdoff,
+	Deleted,
+	Exited,
+};
+
+struct Wctlmesg
+{
+	int		type;
+	Rectangle	r;
+	Image	*image;
+};
+
+struct Conswritemesg
+{
+	Channel	*cw;		/* chan(Stringpair) */
+};
+
+struct Consreadmesg
+{
+	Channel	*c1;		/* chan(tuple(char*, int) == Stringpair) */
+	Channel	*c2;		/* chan(tuple(char*, int) == Stringpair) */
+};
+
+struct Mousereadmesg
+{
+	Channel	*cm;		/* chan(Mouse) */
+};
+
+struct Stringpair	/* rune and nrune or byte and nbyte */
+{
+	void		*s;
+	int		ns;
+};
+
+struct Mousestate
+{
+	Mouse	m;
+	ulong	counter;	/* serial no. of mouse event */
+};
+
+struct Mouseinfo
+{
+	Mousestate	queue[16];
+	int	ri;	/* read index into queue */
+	int	wi;	/* write index */
+	ulong	counter;	/* serial no. of last mouse event we received */
+	ulong	lastcounter;	/* serial no. of last mouse event sent to client */
+	int	lastb;	/* last button state we received */
+	uchar	qfull;	/* filled the queue; no more recording until client comes back */	
+};	
+
+struct Window
+{
+	Ref	ref;
+	QLock	lk;
+	Frame	f;
+	Image		*i;
+	Mousectl		mc;
+	Mouseinfo	mouse;
+	Channel		*ck;			/* chan(Rune[10]) */
+	Channel		*cctl;		/* chan(Wctlmesg)[20] */
+	Channel		*conswrite;	/* chan(Conswritemesg) */
+	Channel		*consread;	/* chan(Consreadmesg) */
+	Channel		*mouseread;	/* chan(Mousereadmesg) */
+	Channel		*wctlread;		/* chan(Consreadmesg) */
+	uint			nr;			/* number of runes in window */
+	uint			maxr;		/* number of runes allocated in r */
+	Rune			*r;
+	uint			nraw;
+	Rune			*raw;
+	uint			org;
+	uint			q0;
+	uint			q1;
+	uint			qh;
+	int			id;
+	char			name[32];
+	uint			namecount;
+	Rectangle		scrollr;
+	/*
+	 * Rio once used originwindow, so screenr could be different from i->r.
+	 * Now they're always the same but the code doesn't assume so.
+	*/
+	Rectangle		screenr;	/* screen coordinates of window */
+	int			resized;
+	int			wctlready;
+	Rectangle		lastsr;
+	int			topped;
+	int			notefd;
+	uchar		scrolling;
+	Cursor		cursor;
+	Cursor		*cursorp;
+	uchar		holding;
+	uchar		rawing;
+	uchar		ctlopen;
+	uchar		wctlopen;
+	uchar		deleted;
+	uchar		mouseopen;
+	char			*label;
+	int			pid;
+	char			*dir;
+};
+
+int		winborder(Window*, Point);
+void		winctl(void*);
+void		winshell(void*);
+Window*	wlookid(int);
+Window*	wmk(Image*, Mousectl*, Channel*, Channel*, int);
+Window*	wpointto(Point);
+Window*	wtop(Point);
+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, Image*);
+int		wctlmesg(Window*, int, Rectangle, Image*);
+uint		wbacknl(Window*, uint, uint);
+uint		winsert(Window*, Rune*, int, uint);
+void		waddraw(Window*, Rune*, int);
+void		wborder(Window*, int);
+void		wclosewin(Window*);
+void		wcurrent(Window*);
+void		wcut(Window*);
+void		wdelete(Window*, uint, uint);
+void		wdoubleclick(Window*, uint*, uint*);
+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		wrefresh(Window*, Rectangle);
+void		wrepaint(Window*);
+void		wresize(Window*, Image*, int);
+void		wscrdraw(Window*);
+void		wscroll(Window*, int);
+void		wselect(Window*);
+void		wsendctlmesg(Window*, int, Rectangle, Image*);
+void		wsetcursor(Window*, int);
+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*);
+
+void		deletetimeoutproc(void*);
+
+struct Timer
+{
+	int		dt;
+	int		cancel;
+	Channel	*c;	/* chan(int) */
+	Timer	*next;
+};
+
+#ifndef Extern
+#define Extern extern
+#endif
+
+Extern	Font		*font;
+Extern	Mousectl	*mousectl;
+Extern	Mouse	*mouse;
+Extern	Keyboardctl	*keyboardctl;
+Extern	Display	*display;
+Extern	Image	*view;
+Extern	Screen	*wscreen;
+Extern	Cursor	boxcursor;
+Extern	Cursor	crosscursor;
+Extern	Cursor	sightcursor;
+Extern	Cursor	whitearrow;
+Extern	Cursor	query;
+Extern	Cursor	*corners[9];
+Extern	Image	*background;
+Extern	Image	*lightgrey;
+Extern	Image	*red;
+Extern	Window	**window;
+Extern	Window	*wkeyboard;	/* window of simulated keyboard */
+Extern	int		nwindow;
+Extern	int		snarffd;
+Extern	Window	*input;
+Extern	QLock	all;			/* BUG */
+Extern	Window	*hidden[100];
+Extern	int		nhidden;
+Extern	int		nsnarf;
+Extern	Rune*	snarf;
+Extern	int		scrolling;
+Extern	int		maxtab;
+Extern	Channel*	winclosechan;
+Extern	Channel*	deletechan;
+Extern	char		*startdir;
+Extern	int		sweeping;
+Extern	int		wctlfd;
+Extern	int		errorshouldabort;
+Extern	int		menuing;		/* menu action is pending; waiting for window to be indicated */
+Extern	int		snarfversion;	/* updated each time it is written */
+Extern	int		messagesize;		/* negotiated in 9P version setup */
blob - /dev/null
blob + 86e589b3e9ad5d736c0456347d2dc54b14b4de5a (mode 644)
--- /dev/null
+++ src/cmd/9term/data.c
@@ -0,0 +1,180 @@
+#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 "dat.h"
+#include "fns.h"
+
+Cursor crosscursor = {
+	{-7, -7},
+	{0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+	 0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
+	 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
+	{0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
+	 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, }
+};
+
+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, }
+};
+
+Cursor sightcursor = {
+	{-7, -7},
+	{0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+	 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+	 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
+	{0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+	 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+	 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+	 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
+};
+
+Cursor whitearrow = {
+	{0, 0},
+	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
+	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
+	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
+	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
+	{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C, 
+	 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C, 
+	 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C, 
+	 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
+};
+
+Cursor query = {
+	{-7,-7},
+	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 
+	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, 
+	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, 
+	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
+	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, 
+	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, 
+	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, 
+	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor tl = {
+	{-4, -4},
+	{0xfe, 0x00, 0x82, 0x00, 0x8c, 0x00, 0x87, 0xff, 
+	 0xa0, 0x01, 0xb0, 0x01, 0xd0, 0x01, 0x11, 0xff, 
+	 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 
+	 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x1f, 0x00, },
+	{0x00, 0x00, 0x7c, 0x00, 0x70, 0x00, 0x78, 0x00, 
+	 0x5f, 0xfe, 0x4f, 0xfe, 0x0f, 0xfe, 0x0e, 0x00, 
+	 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 
+	 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, }
+};
+
+Cursor t = {
+	{-7, -8},
+	{0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x06, 0xc0, 
+	 0x1c, 0x70, 0x10, 0x10, 0x0c, 0x60, 0xfc, 0x7f, 
+	 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xff, 0xff, 
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 
+	 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80, 0x03, 0x80, 
+	 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x00, 0x00, 
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor tr = {
+	{-11, -4},
+	{0x00, 0x7f, 0x00, 0x41, 0x00, 0x31, 0xff, 0xe1, 
+	 0x80, 0x05, 0x80, 0x0d, 0x80, 0x0b, 0xff, 0x88, 
+	 0x00, 0x88, 0x0, 0x88, 0x00, 0x88, 0x00, 0x88, 
+	 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf8, },
+	{0x00, 0x00, 0x00, 0x3e, 0x00, 0x0e, 0x00, 0x1e, 
+	 0x7f, 0xfa, 0x7f, 0xf2, 0x7f, 0xf0, 0x00, 0x70, 
+	 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 
+	 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, }
+};
+
+Cursor r = {
+	{-8, -7},
+	{0x07, 0xc0, 0x04, 0x40, 0x04, 0x40, 0x04, 0x58, 
+	 0x04, 0x68, 0x04, 0x6c, 0x04, 0x06, 0x04, 0x02, 
+	 0x04, 0x06, 0x04, 0x6c, 0x04, 0x68, 0x04, 0x58, 
+	 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x07, 0xc0, },
+	{0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 
+	 0x03, 0x90, 0x03, 0x90, 0x03, 0xf8, 0x03, 0xfc, 
+	 0x03, 0xf8, 0x03, 0x90, 0x03, 0x90, 0x03, 0x80, 
+	 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor br = {
+	{-11, -11},
+	{0x00, 0xf8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 
+	 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 
+	 0xff, 0x88, 0x80, 0x0b, 0x80, 0x0d, 0x80, 0x05, 
+	 0xff, 0xe1, 0x00, 0x31, 0x00, 0x41, 0x00, 0x7f, },
+	{0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 
+	 0x0, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 
+	 0x00, 0x70, 0x7f, 0xf0, 0x7f, 0xf2, 0x7f, 0xfa, 
+	 0x00, 0x1e, 0x00, 0x0e, 0x00, 0x3e, 0x00, 0x00, }
+};
+
+Cursor b = {
+	{-7, -7},
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	 0xff, 0xff, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 
+	 0xfc, 0x7f, 0x0c, 0x60, 0x10, 0x10, 0x1c, 0x70, 
+	 0x06, 0xc0, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, },
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	 0x00, 0x00, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 
+	 0x03, 0x80, 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80, 
+	 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor bl = {
+	{-4, -11},
+	{0x1f, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 
+	 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 
+	 0x11, 0xff, 0xd0, 0x01, 0xb0, 0x01, 0xa0, 0x01, 
+	 0x87, 0xff, 0x8c, 0x00, 0x82, 0x00, 0xfe, 0x00, },
+	{0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 
+	 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 
+	 0x0e, 0x00, 0x0f, 0xfe, 0x4f, 0xfe, 0x5f, 0xfe, 
+	 0x78, 0x00, 0x70, 0x00, 0x7c, 0x00, 0x00, 0x0, }
+};
+
+Cursor l = {
+	{-7, -7},
+	{0x03, 0xe0, 0x02, 0x20, 0x02, 0x20, 0x1a, 0x20, 
+	 0x16, 0x20, 0x36, 0x20, 0x60, 0x20, 0x40, 0x20, 
+	 0x60, 0x20, 0x36, 0x20, 0x16, 0x20, 0x1a, 0x20, 
+	 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x03, 0xe0, },
+	{0x00, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 
+	 0x09, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0, 
+	 0x1f, 0xc0, 0x09, 0xc0, 0x09, 0xc0, 0x01, 0xc0, 
+	 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, }
+};
+
+Cursor *corners[9] = {
+	&tl,	&t,	&tr,
+	&l,	nil,	&r,
+	&bl,	&b,	&br,
+};
+
+void
+iconinit(void)
+{
+	background = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x777777FF);
+	red = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0xDD0000FF);
+}
blob - /dev/null
blob + 9fc3093323ba57b65b1161a4a2a6266b27694d09 (mode 644)
--- /dev/null
+++ src/cmd/9term/fns.h
@@ -0,0 +1,36 @@
+#undef isalnum
+#define isalnum runeisalnum
+
+void	keyboardsend(char*, int);
+int	whide(Window*);
+int	wunhide(int);
+void	freescrtemps(void);
+int	parsewctl(char**, Rectangle, Rectangle*, int*, int*, int*, int*, char**, char*, char*);
+Window *new(Image*, int, int, int, char*, char*, char**);
+void	riosetcursor(Cursor*, int);
+int	min(int, int);
+int	max(int, int);
+Rune*	strrune(Rune*, Rune);
+int	isalnum(Rune);
+void	timerstop(Timer*);
+void	timercancel(Timer*);
+Timer*	timerstart(int);
+void	error(char*);
+void	killprocs(void);
+int	shutdown(void*, char*);
+void	iconinit(void);
+void	*erealloc(void*, uint);
+void *emalloc(uint);
+char *estrdup(char*);
+void	button3menu(void);
+void	button2menu(Window*);
+void	cvttorunes(char*, int, Rune*, int*, int*, int*);
+/* was (byte*,int)	runetobyte(Rune*, int); */
+char* runetobyte(Rune*, int, int*);
+void	timerinit(void);
+int	goodrect(Rectangle);
+int	rawon(void);
+
+#define	runemalloc(n)		malloc((n)*sizeof(Rune))
+#define	runerealloc(a, n)	realloc(a, (n)*sizeof(Rune))
+#define	runemove(a, b, n)	memmove(a, b, (n)*sizeof(Rune))
blob - /dev/null
blob + 130942bdc38851ebb5305223c40ef6f374d1e943 (mode 644)
--- /dev/null
+++ src/cmd/9term/malloc.c
@@ -0,0 +1,63 @@
+/*
+ * These are here mainly so that I can link against
+ * debugmalloc.c instead and not recompile the world.
+ */
+
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+static Lock malloclock;
+
+void*
+p9malloc(ulong n)
+{
+	void *v;
+	
+	if(n == 0)
+		n++;
+	lock(&malloclock);
+	v = malloc(n);
+	unlock(&malloclock);
+	print("p9malloc %lud => %p; pc %lux\n", n, v, getcallerpc(&n));
+	return v;
+}
+
+void
+p9free(void *v)
+{
+	if(v == nil)
+		return;
+	lock(&malloclock);
+	print("p9free %p; pc %lux\n", v, getcallerpc(&v));
+	free(v);
+	unlock(&malloclock);
+}
+
+void*
+p9calloc(ulong a, ulong b)
+{
+	void *v;
+	
+	if(a*b == 0)
+		a = b = 1;
+
+	lock(&malloclock);
+	v = calloc(a*b, 1);
+	unlock(&malloclock);
+	print("p9calloc %lud %lud => %p; pc %lux\n", a, b, v, getcallerpc(&a));
+	return v;
+}
+
+void*
+p9realloc(void *v, ulong n)
+{
+	void *vv;
+	
+	lock(&malloclock);
+	vv = realloc(v, n);
+	unlock(&malloclock);
+	print("p9realloc %p %lud => %p; pc %lux\n", v, n, vv, getcallerpc(&v));
+	return vv;
+}
+
blob - /dev/null
blob + 8a614ad66016c170b16edb7ed78388836f5c0b90 (mode 644)
--- /dev/null
+++ src/cmd/9term/scrl.c
@@ -0,0 +1,183 @@
+#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 "dat.h"
+#include "fns.h"
+
+static Image *scrtmp;
+
+static
+void
+scrtemps(void)
+{
+	int h;
+
+	if(scrtmp)
+		return;
+	h = BIG*Dy(screen->r);
+	scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, DWhite);
+	if(scrtmp == nil)
+		error("scrtemps");
+}
+
+void
+freescrtemps(void)
+{
+	freeimage(scrtmp);
+	scrtmp = nil;
+}
+
+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
+wscrdraw(Window *w)
+{
+	Rectangle r, r1, r2;
+	Image *b;
+
+	scrtemps();
+	if(w->i == nil)
+		error("scrdraw");
+	r = w->scrollr;
+	b = scrtmp;
+	r1 = r;
+	r1.min.x = 0;
+	r1.max.x = Dx(r);
+	r2 = scrpos(r1, w->org, w->org+w->f.nchars, w->nr);
+	if(!eqrect(r2, w->lastsr)){
+		w->lastsr = r2;
+		/* move r1, r2 to (0,0) to avoid clipping */
+		r2 = rectsubpt(r2, r1.min);
+		r1 = rectsubpt(r1, r1.min);
+		draw(b, r1, w->f.cols[BORD], nil, ZP);
+		draw(b, r2, w->f.cols[BACK], nil, ZP);
+		r2.min.x = r2.max.x-1;
+		draw(b, r2, w->f.cols[BORD], nil, ZP);
+		draw(w->i, r, b, nil, Pt(0, r1.min.y));
+	}
+}
+
+void
+wscrsleep(Window *w, uint dt)
+{
+	Timer	*timer;
+	int y, b;
+	static Alt alts[3];
+
+	timer = timerstart(dt);
+	y = w->mc.m.xy.y;
+	b = w->mc.m.buttons;
+	alts[0].c = timer->c;
+	alts[0].v = nil;
+	alts[0].op = CHANRCV;
+	alts[1].c = w->mc.c;
+	alts[1].v = &w->mc.m;
+	alts[1].op = CHANRCV;
+	alts[2].op = CHANEND;
+	for(;;)
+		switch(alt(alts)){
+		case 0:
+			timerstop(timer);
+			return;
+		case 1:
+			if(abs(w->mc.m.xy.y-y)>2 || w->mc.m.buttons!=b){
+				timercancel(timer);
+				return;
+			}
+			break;
+		}
+}
+
+void
+wscroll(Window *w, int but)
+{
+	uint p0, oldp0;
+	Rectangle s;
+	int x, y, my, h, first;
+
+	s = insetrect(w->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(w->mc.m.xy.x<s.min.x || s.max.x<=w->mc.m.xy.x){
+			readmouse(&w->mc);
+		}else{
+			my = w->mc.m.xy.y;
+			if(my < s.min.y)
+				my = s.min.y;
+			if(my >= s.max.y)
+				my = s.max.y;
+			if(!eqpt(w->mc.m.xy, Pt(x, my))){
+				wmovemouse(w, Pt(x, my));
+				readmouse(&w->mc);		/* absorb event generated by moveto() */
+			}
+			if(but == 2){
+				y = my;
+				if(y > s.max.y-2)
+					y = s.max.y-2;
+				if(w->nr > 1024*1024)
+					p0 = ((w->nr>>10)*(y-s.min.y)/h)<<10;
+				else
+					p0 = w->nr*(y-s.min.y)/h;
+				if(oldp0 != p0)
+					wsetorigin(w, p0, FALSE);
+				oldp0 = p0;
+				readmouse(&w->mc);
+				continue;
+			}
+			if(but == 1)
+				p0 = wbacknl(w, w->org, (my-s.min.y)/w->f.font->height);
+			else
+				p0 = w->org+frcharofpt(&w->f, Pt(s.max.x, my));
+			if(oldp0 != p0)
+				wsetorigin(w, p0, TRUE);
+			oldp0 = p0;
+			/* debounce */
+			if(first){
+				flushimage(display, 1);
+				sleep(200);
+				nbrecv(w->mc.c, &w->mc.m);
+				first = FALSE;
+			}
+			wscrsleep(w, 100);
+		}
+	}while(w->mc.m.buttons & (1<<(but-1)));
+	while(w->mc.m.buttons)
+		readmouse(&w->mc);
+}
blob - /dev/null
blob + 5dedf144dd509586d38b94ba58a27d9ef57c3f72 (mode 644)
--- /dev/null
+++ src/cmd/9term/time.c
@@ -0,0 +1,125 @@
+#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 "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 *a)
+{
+	int i, nt, na, dt, del;
+	Timer **t, *x;
+	uint old, new;
+
+	USED(a);
+	rfork(RFFDG);
+	threadsetname("TIMERPROC");
+	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 = 0;
+			if(x->cancel){
+				timerstop(x);
+				del = 1;
+			}else if(x->dt <= 0){
+				/*
+				 * avoid possible deadlock if client is
+				 * now sending on ctimer
+				 */
+				if(nbsendul(x->c, 0) > 0)
+					del = 1;
+			}
+			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)
+					abort();
+			}
+			t[nt++] = x;
+			old = msec();
+		}
+		if(nbrecv(ctimer, &x) > 0)
+			goto gotit;
+	}
+}
+
+void
+timerinit(void)
+{
+	ctimer = chancreate(sizeof(Timer*), 100);
+	proccreate(timerproc, nil, STACK);
+}
+
+/*
+ * timeralloc() and timerfree() don't lock, so can only be
+ * called from the main proc.
+ */
+
+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 + 8a35d1d75625c85dde4f582adc68297cb300d940 (mode 644)
--- /dev/null
+++ src/cmd/9term/util.c
@@ -0,0 +1,149 @@
+#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 "dat.h"
+#include "fns.h"
+
+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, "rio: %s: %r\n", s);
+	if(errorshouldabort)
+		abort();
+	threadexitsall("error");
+}
+
+void*
+erealloc(void *p, uint n)
+{
+	p = realloc(p, n);
+	if(p == nil)
+		error("realloc failed");
+	return p;
+}
+
+void*
+emalloc(uint n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil)
+		error("malloc failed");
+	memset(p, 0, n);
+	return p;
+}
+
+char*
+estrdup(char *s)
+{
+	char *p;
+
+	p = malloc(strlen(s)+1);
+	if(p == nil)
+		error("strdup failed");
+	strcpy(p, s);
+	return p;
+}
+
+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;
+}
+
+Rune*
+strrune(Rune *s, Rune c)
+{
+	Rune c1;
+
+	if(c == 0) {
+		while(*s++)
+			;
+		return s-1;
+	}
+
+	while(c1 = *s++)
+		if(c1 == c)
+			return s-1;
+	return nil;
+}
+
+int
+min(int a, int b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+int
+max(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+char*
+runetobyte(Rune *r, int n, int *ip)
+{
+	char *s;
+	int m;
+
+	s = emalloc(n*UTFmax+1);
+	m = snprint(s, n*UTFmax+1, "%.*S", n, r);
+	*ip = m;
+	return s;
+}
+
blob - /dev/null
blob + 600a4e42187f377973c137129e3442e6148d8d37 (mode 644)
--- /dev/null
+++ src/cmd/9term/wind.c
@@ -0,0 +1,1628 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+#define MOVEIT if(0)
+
+enum
+{
+	HiWater	= 640000,	/* max size of history */
+	LoWater	= 400000,	/* min size of history after max'ed */
+	MinWater	= 20000,	/* room to leave available when reallocating */
+};
+
+static	int		topped;
+static	int		id;
+
+static	Image	*cols[NCOL];
+static	Image	*grey;
+static	Image	*darkgrey;
+static	Cursor	*lastcursor;
+static	Image	*titlecol;
+static	Image	*lighttitlecol;
+static	Image	*holdcol;
+static	Image	*lightholdcol;
+static	Image	*paleholdcol;
+
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+	Window *w;
+	Rectangle r;
+
+	if(cols[0] == nil){
+		/* greys are multiples of 0x11111100+0xFF, 14* being palest */
+		grey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
+		darkgrey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF);
+		cols[BACK] = display->white;
+		cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
+		cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x999999FF);
+		cols[TEXT] = display->black;
+		cols[HTEXT] = display->black;
+		titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreygreen);
+		lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreygreen);
+		holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedblue);
+		lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
+		paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
+	}
+	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->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
+	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
+	w->scrollr = r;
+	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->lastsr = ZR;
+	r.min.x += Scrollwid+Scrollgap;
+	frinit(&w->f, r, font, i, cols);
+	w->f.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->f.entire.min);
+	wborder(w, Selborder);
+	wscrdraw(w);
+	incref(&w->ref);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
+	return w;
+}
+
+void
+wsetname(Window *w)
+{
+	int i, n;
+	char err[ERRMAX];
+	
+	n = sprint(w->name, "window.%d.%d", w->id, w->namecount++);
+	for(i='A'; i<='Z'; i++){
+		if(nameimage(w->i, w->name, 1) > 0)
+			return;
+		errstr(err, sizeof err);
+		if(strcmp(err, "image name in use") != 0)
+			break;
+		w->name[n] = i;
+		w->name[n+1] = 0;
+	}
+	w->name[0] = 0;
+	fprint(2, "rio: setname failed: %s\n", err);
+}
+
+void
+wresize(Window *w, Image *i, int move)
+{
+	Rectangle r, or;
+
+	or = w->i->r;
+	if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r)))
+		draw(i, i->r, w->i, nil, w->i->r.min);
+	if(w->i != i){
+fprint(2, "res %p %p\n", w->i, i);
+		freeimage(w->i);
+		w->i = i;
+	}
+//	wsetname(w);
+//XXX	w->mc.image = i;
+	r = insetrect(i->r, Selborder+1);
+	w->scrollr = r;
+	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->lastsr = ZR;
+	r.min.x += Scrollwid+Scrollgap;
+	if(move)
+		frsetrects(&w->f, r, w->i);
+	else{
+		frclear(&w->f, FALSE);
+		frinit(&w->f, r, w->f.font, w->i, cols);
+		wsetcols(w);
+		w->f.maxtab = maxtab*stringwidth(w->f.font, "0");
+		r = insetrect(w->i->r, Selborder);
+		draw(w->i, r, cols[BACK], nil, w->f.entire.min);
+		wfill(w);
+		wsetselect(w, w->q0, w->q1);
+		wscrdraw(w);
+	}
+	wborder(w, Selborder);
+	w->topped = ++topped;
+	w->resized = TRUE;
+	w->mouse.counter++;
+}
+
+void
+wrefresh(Window *w, Rectangle r)
+{
+	/* USED(r); */
+
+	/* BUG: rectangle is ignored */
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
+	if(w->mouseopen)
+		return;
+	draw(w->i, insetrect(w->i->r, Borderwidth), w->f.cols[BACK], nil, w->i->r.min);
+	w->f.ticked = 0;
+	if(w->f.p0 > 0)
+		frdrawsel(&w->f, frptofchar(&w->f, 0), 0, w->f.p0, 0);
+	if(w->f.p1 < w->f.nchars)
+		frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, w->f.nchars, 0);
+	frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 1);
+	w->lastsr = ZR;
+	wscrdraw(w);
+}
+
+int
+wclose(Window *w)
+{
+	int i;
+
+	i = decref(&w->ref);
+	if(i > 0)
+		return 0;
+	if(i < 0)
+		error("negative ref count");
+	if(!w->deleted)
+		wclosewin(w);
+	wsendctlmesg(w, Exited, ZR, nil);
+	return 1;
+}
+
+
+void
+winctl(void *arg)
+{
+	Rune *rp, *bp, *tp, *up, *kbdr;
+	uint qh;
+	int nr, nb, c, wid, i, npart, initial, lastb;
+	char *s, *t, part[3];
+	Window *w;
+	Mousestate *mp, m;
+	enum { WKey, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, NWALT };
+	Alt alts[NWALT+1];
+	Mousereadmesg mrm;
+	Conswritemesg cwm;
+	Consreadmesg crm;
+	Consreadmesg cwrm;
+	Stringpair pair;
+	Wctlmesg wcm;
+	char buf[4*12+1];
+
+	w = arg;
+	snprint(buf, sizeof buf, "winctl-id%d", w->id);
+	threadsetname(buf);
+
+	mrm.cm = chancreate(sizeof(Mouse), 0);
+	cwm.cw = chancreate(sizeof(Stringpair), 0);
+	crm.c1 = chancreate(sizeof(Stringpair), 0);
+	crm.c2 = chancreate(sizeof(Stringpair), 0);
+	cwrm.c1 = chancreate(sizeof(Stringpair), 0);
+	cwrm.c2 = chancreate(sizeof(Stringpair), 0);
+	
+
+	alts[WKey].c = w->ck;
+	alts[WKey].v = &kbdr;
+	alts[WKey].op = CHANRCV;
+	alts[WMouse].c = w->mc.c;
+	alts[WMouse].v = &w->mc.m;
+	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 = &cwrm;
+	alts[WWread].op = CHANSND;
+	alts[NWALT].op = CHANEND;
+
+	npart = 0;
+	lastb = -1;
+	for(;;){
+		if(w->mouseopen && w->mouse.counter != w->mouse.lastcounter)
+			alts[WMouseread].op = CHANSND;
+		else
+			alts[WMouseread].op = CHANNOP;
+		if(!w->scrolling && !w->mouseopen && w->qh>w->org+w->f.nchars)
+			alts[WCwrite].op = CHANNOP;
+		else
+			alts[WCwrite].op = CHANSND;
+		if(w->deleted || !w->wctlready)
+			alts[WWread].op = CHANNOP;
+		else
+			alts[WWread].op = CHANSND;
+		/* 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 WKey:
+			for(i=0; kbdr[i]!=L'\0'; i++)
+				wkeyctl(w, kbdr[i]);
+//			wkeyctl(w, r);
+//			while(nbrecv(w->ck, &r))
+//				wkeyctl(w, r);
+			break;
+		case WMouse:
+			if(w->mouseopen) {
+				w->mouse.counter++;
+
+				/* queue click events */
+				if(!w->mouse.qfull && lastb != w->mc.m.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->m = w->mc.m;
+					mp->counter = w->mouse.counter;
+					lastb = w->mc.m.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.m, w->mouse.counter};
+
+			w->mouse.lastcounter = m.counter;
+			send(mrm.cm, &m.m);
+			continue;
+		case WCtl:
+			if(wctlmesg(w, wcm.type, wcm.r, wcm.image) == Exited){
+				chanfree(crm.c1);
+				chanfree(crm.c2);
+				chanfree(mrm.cm);
+				chanfree(cwm.cw);
+				chanfree(cwrm.c1);
+				chanfree(cwrm.c2);
+				threadexits(nil);
+			}
+			continue;
+		case WCwrite:
+			recv(cwm.cw, &pair);
+			rp = pair.s;
+			nr = pair.ns;
+			bp = rp;
+			for(i=0; i<nr; i++)
+				if(*bp++ == '\b'){
+					--bp;
+					initial = 0;
+					tp = runemalloc(nr);
+					runemove(tp, rp, i);
+					up = tp+i;
+					for(; i<nr; i++){
+						*up = *bp++;
+						if(*up == '\b')
+							if(up == tp)
+								initial++;
+							else
+								--up;
+						else
+							up++;
+					}
+					if(initial){
+						if(initial > w->qh)
+							initial = w->qh;
+						qh = w->qh-initial;
+						wdelete(w, qh, qh+initial);
+						w->qh = qh;
+					}
+					free(rp);
+					rp = tp;
+					nr = up-tp;
+					rp[nr] = 0;
+					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(cwrm.c1, &pair);
+			if(w->deleted || w->i==nil)
+				pair.ns = sprint(pair.s, "");
+			else{
+				s = "visible";
+				for(i=0; i<nhidden; i++)
+					if(hidden[i] == w){
+						s = "hidden";
+						break;
+					}
+				t = "notcurrent";
+				if(w == input)
+					t = "current";
+				pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %s %s ",
+					w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+			}
+			send(cwrm.c2, &pair);
+			continue;
+		}
+		if(!w->deleted)
+			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
+interruptproc(void *v)
+{
+	int *notefd;
+
+	notefd = v;
+	write(*notefd, "interrupt", 9);
+	free(notefd);
+}
+
+int
+windfilewidth(Window *w, uint q0, int oneelement)
+{
+	uint q;
+	Rune r;
+
+	q = q0;
+	while(q > 0){
+		r = w->r[q-1];
+		if(r<=' ')
+			break;
+		if(oneelement && r=='/')
+			break;
+		--q;
+	}
+	return q0-q;
+}
+
+void
+showcandidates(Window *w, Completion *c)
+{
+	int i;
+	Fmt f;
+	Rune *rp;
+	uint nr, qline, q0;
+	char *s;
+
+	runefmtstrinit(&f);
+	if (c->nmatch == 0)
+		s = "[no matches in ";
+	else
+		s = "[";
+	if(c->nfile > 32)
+		fmtprint(&f, "%s%d files]\n", s, c->nfile);
+	else{
+		fmtprint(&f, "%s", s);
+		for(i=0; i<c->nfile; i++){
+			if(i > 0)
+				fmtprint(&f, " ");
+			fmtprint(&f, "%s", c->filename[i]);
+		}
+		fmtprint(&f, "]\n");
+	}
+	/* place text at beginning of line before host point */
+	qline = w->qh;
+	while(qline>0 && w->r[qline-1] != '\n')
+		qline--;
+
+	rp = runefmtstrflush(&f);
+	nr = runestrlen(rp);
+
+	q0 = w->q0;
+	q0 += winsert(w, rp, runestrlen(rp), qline) - qline;
+	free(rp);
+	wsetselect(w, q0+nr, q0+nr);
+}
+
+Rune*
+namecomplete(Window *w)
+{
+	int nstr, npath;
+	Rune *rp, *path, *str;
+	Completion *c;
+	char *s, *dir, *root;
+
+	/* 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 nil;
+	nstr = windfilewidth(w, w->q0, TRUE);
+	str = runemalloc(nstr);
+	runemove(str, w->r+(w->q0-nstr), nstr);
+	npath = windfilewidth(w, w->q0-nstr, FALSE);
+	path = runemalloc(npath);
+	runemove(path, w->r+(w->q0-nstr-npath), npath);
+	rp = nil;
+
+	/* is path rooted? if not, we need to make it relative to window path */
+	if(npath>0 && path[0]=='/'){
+		dir = malloc(UTFmax*npath+1);
+		sprint(dir, "%.*S", npath, path);
+	}else{
+		if(strcmp(w->dir, "") == 0)
+			root = ".";
+		else
+			root = w->dir;
+		dir = malloc(strlen(root)+1+UTFmax*npath+1);
+		sprint(dir, "%s/%.*S", root, npath, path);
+	}
+	dir = cleanname(dir);
+
+	s = smprint("%.*S", nstr, str);
+	c = complete(dir, s);
+	free(s);
+	if(c == nil)
+		goto Return;
+
+	if(!c->advance)
+		showcandidates(w, c);
+
+	if(c->advance)
+		rp = runesmprint("%s", c->string);
+
+  Return:
+	freecompletion(c);
+	free(dir);
+	free(path);
+	free(str);
+	return rp;
+}
+
+void
+wkeyctl(Window *w, Rune r)
+{
+	uint q0 ,q1;
+	int n, nb, nr;
+	Rune *rp;
+	int *notefd;
+
+	if(r == 0)
+		return;
+	if(w->deleted)
+		return;
+	w->rawing = rawon();
+	/* navigation keys work only when mouse is not open */
+	if(!w->mouseopen)
+		switch(r){
+		case Kdown:
+			n = w->f.maxlines/3;
+			goto case_Down;
+		case Kscrollonedown:
+			n = mousescrollsize(w->f.maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Down;
+		case Kpgdown:
+			n = 2*w->f.maxlines/3;
+		case_Down:
+			q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+n*w->f.font->height));
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kup:
+			n = w->f.maxlines/3;
+			goto case_Up;
+		case Kscrolloneup:
+			n = mousescrollsize(w->f.maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Up;
+		case Kpgup:
+			n = 2*w->f.maxlines/3;
+		case_Up:
+			q0 = wbacknl(w, w->org, n);
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kleft:
+			if(w->q0 > 0){
+				q0 = w->q0-1;
+				wsetselect(w, q0, q0);
+				wshow(w, q0);
+			}
+			return;
+		case Kright:
+			if(w->q1 < w->nr){
+				q1 = w->q1+1;
+				wsetselect(w, q1, q1);
+				wshow(w, q1);
+			}
+			return;
+		case Khome:
+			wshow(w, 0);
+			return;
+		case Kend:
+			wshow(w, w->nr);
+			return;
+		case 0x01:	/* ^A: beginning of line */
+			if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
+				return;
+			nb = wbswidth(w, 0x15 /* ^U */);
+			wsetselect(w, w->q0-nb, w->q0-nb);
+			wshow(w, w->q0);
+			return;
+		case 0x05:	/* ^E: end of line */
+			q0 = w->q0;
+			while(q0 < w->nr && w->r[q0]!='\n')
+				q0++;
+			wsetselect(w, q0, q0);
+			wshow(w, w->q0);
+			return;
+		}
+	/*
+	 * This if used to be below the if(w->rawing ...),
+	 * but let's try putting it here.  This will allow ESC-processing
+	 * to toggle hold mode even in remote SSH connections.
+	 * The drawback is that vi-style processing gets harder.
+	 * If you find yourself in some weird readline mode, good
+	 * luck getting out without ESC.  Let's see who complains.
+	 */
+	if(r==0x1B || (w->holding && r==0x7F)){	/* toggle hold */
+		if(w->holding)
+			--w->holding;
+		else
+			w->holding++;
+		wrepaint(w);
+		if(r == 0x1B)
+			return;
+	}
+	if(!w->holding && w->rawing && (w->q0==w->nr || w->mouseopen)){
+		waddraw(w, &r, 1);
+		return;
+	}
+	if(r != 0x7F){
+		wsnarf(w);
+		wcut(w);
+	}
+	switch(r){
+	case 0x7F:		/* send interrupt */
+		w->qh = w->nr;
+		wshow(w, w->qh);
+		notefd = emalloc(sizeof(int));
+		*notefd = w->notefd;
+		proccreate(interruptproc, notefd, 4096);
+		return;
+	case 0x06:	/* ^F: file name completion */
+	case Kins:		/* Insert: file name completion */
+		rp = namecomplete(w);
+		if(rp == nil)
+			return;
+		nr = runestrlen(rp);
+		q0 = w->q0;
+		q0 = winsert(w, rp, nr, q0);
+		wshow(w, q0+nr);
+		free(rp);
+		return;
+	case 0x08:	/* ^H: erase character */
+	case 0x15:	/* ^U: erase line */
+	case 0x17:	/* ^W: erase word */
+		if(w->q0==0 || w->q0==w->qh)
+			return;
+		nb = wbswidth(w, r);
+		q1 = w->q0;
+		q0 = q1-nb;
+		if(q0 < w->org){
+			q0 = w->org;
+			nb = q1-q0;
+		}
+		if(nb > 0){
+			wdelete(w, q0, q0+nb);
+			wsetselect(w, q0, q0);
+		}
+		return;
+	}
+	/* otherwise ordinary character; just insert */
+	q0 = w->q0;
+	q0 = winsert(w, &r, 1, q0);
+	wshow(w, q0+1);
+}
+
+void
+wsetcols(Window *w)
+{
+	if(w->holding)
+		if(w == input)
+			w->f.cols[TEXT] = w->f.cols[HTEXT] = holdcol;
+		else
+			w->f.cols[TEXT] = w->f.cols[HTEXT] = lightholdcol;
+	else
+		if(w == input)
+			w->f.cols[TEXT] = w->f.cols[HTEXT] = display->black;
+		else
+			w->f.cols[TEXT] = w->f.cols[HTEXT] = darkgrey;
+}
+
+void
+wrepaint(Window *w)
+{
+	wsetcols(w);
+	if(!w->mouseopen)
+		_frredraw(&w->f, w->f.r.min);
+	if(w == input){
+		wborder(w, Selborder);
+		wsetcursor(w, 0);
+	}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);
+//XXX	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++;
+		sprint(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, 1);
+		sleep(300);
+		riosetcursor(c, 1);
+	}
+	plumbfree(m);
+}
+
+int
+winborder(Window *w, Point xy)
+{
+	return ptinrect(xy, w->screenr) && !ptinrect(xy, insetrect(w->screenr, Selborder));
+}
+
+void
+wmousectl(Window *w)
+{
+	int but;
+
+	if(w->mc.m.buttons == 1)
+		but = 1;
+	else if(w->mc.m.buttons == 2)
+		but = 2;
+	else if(w->mc.m.buttons == 4)
+		but = 3;
+	else{
+		if(w->mc.m.buttons == 8)
+			wkeyctl(w, Kscrolloneup);
+		if(w->mc.m.buttons == 16)
+			wkeyctl(w, Kscrollonedown);
+		return;
+	}
+
+	incref(&w->ref);		/* hold up window while we track */
+	if(w->deleted)
+		goto Return;
+	if(ptinrect(w->mc.m.xy, w->scrollr)){
+		if(but)
+			wscroll(w, but);
+		goto Return;
+	}
+	if(but == 1)
+		wselect(w);
+	/* else all is handled by main process */
+   Return:
+	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->f.nchars){
+		p1 = q1 - w->org;
+		if(p1 > w->f.nchars)
+			p1 = w->f.nchars;
+		if(q0 < w->org){
+			w->org = q0;
+			p0 = 0;
+		}else
+			p0 = q0 - w->org;
+		frdelete(&w->f, p0, p1);
+		wfill(w);
+	}
+}
+
+
+static Window	*clickwin;
+static uint	clickmsec;
+static Window	*selectwin;
+static uint	selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+	if(f != &selectwin->f)
+		error("frameselect not right frame");
+	wframescroll(selectwin, dl);
+}
+
+void
+wframescroll(Window *w, int dl)
+{
+	uint q0;
+
+	if(dl == 0){
+		wscrsleep(w, 100);
+		return;
+	}
+	if(dl < 0){
+		q0 = wbacknl(w, w->org, -dl);
+		if(selectq > w->org+w->f.p0)
+			wsetselect(w, w->org+w->f.p0, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->f.p0);
+	}else{
+		if(w->org+w->f.nchars == w->nr)
+			return;
+		q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+dl*w->f.font->height));
+		if(selectq >= w->org+w->f.p1)
+			wsetselect(w, w->org+w->f.p1, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->f.p1);
+	}
+	wsetorigin(w, q0, TRUE);
+}
+
+void
+wselect(Window *w)
+{
+	uint q0, q1;
+	int b, x, y, first;
+
+	first = 1;
+	selectwin = w;
+	/*
+	 * Double-click immediately if it might make sense.
+	 */
+	b = w->mc.m.buttons;
+	q0 = w->q0;
+	q1 = w->q1;
+	selectq = w->org+frcharofpt(&w->f, w->mc.m.xy);
+	if(clickwin==w && w->mc.m.msec-clickmsec<500)
+	if(q0==q1 && selectq==w->q0){
+		wdoubleclick(w, &q0, &q1);
+		wsetselect(w, q0, q1);
+		flushimage(display, 1);
+		x = w->mc.m.xy.x;
+		y = w->mc.m.xy.y;
+		/* stay here until something interesting happens */
+		do
+			readmouse(&w->mc);
+		while(w->mc.m.buttons==b && abs(w->mc.m.xy.x-x)<3 && abs(w->mc.m.xy.y-y)<3);
+		w->mc.m.xy.x = x;	/* in case we're calling frselect */
+		w->mc.m.xy.y = y;
+		q0 = w->q0;	/* may have changed */
+		q1 = w->q1;
+		selectq = q0;
+	}
+	if(w->mc.m.buttons == b){
+		w->f.scroll = framescroll;
+		frselect(&w->f, &w->mc);
+		/* horrible botch: while asleep, may have lost selection altogether */
+		if(selectq > w->nr)
+			selectq = w->org + w->f.p0;
+		w->f.scroll = nil;
+		if(selectq < w->org)
+			q0 = selectq;
+		else
+			q0 = w->org + w->f.p0;
+		if(selectq > w->org+w->f.nchars)
+			q1 = selectq;
+		else
+			q1 = w->org+w->f.p1;
+	}
+	if(q0 == q1){
+		if(q0==w->q0 && clickwin==w && w->mc.m.msec-clickmsec<500){
+			wdoubleclick(w, &q0, &q1);
+			clickwin = nil;
+		}else{
+			clickwin = w;
+			clickmsec = w->mc.m.msec;
+		}
+	}else
+		clickwin = nil;
+	wsetselect(w, q0, q1);
+	flushimage(display, 1);
+	while(w->mc.m.buttons){
+		w->mc.m.msec = 0;
+		b = w->mc.m.buttons;
+		if(b & 6){
+			if(b & 2){
+				wsnarf(w);
+				wcut(w);
+			}else{
+				if(first){
+					first = 0;
+					getsnarf();
+				}
+				wpaste(w);
+			}
+		}
+		wscrdraw(w);
+		flushimage(display, 1);
+		while(w->mc.m.buttons == b)
+			readmouse(&w->mc);
+		clickwin = nil;
+	}
+}
+
+void
+wsendctlmesg(Window *w, int type, Rectangle r, Image *image)
+{
+	Wctlmesg wcm;
+
+	wcm.type = type;
+	wcm.r = r;
+	wcm.image = image;
+	send(w->cctl, &wcm);
+}
+
+int
+wctlmesg(Window *w, int m, Rectangle r, Image *i)
+{
+	char buf[64];
+
+	switch(m){
+	default:
+		error("unknown control message");
+		break;
+	case Wakeup:
+		break;
+	case Moved:
+	case Reshaped:
+		if(w->deleted){
+			freeimage(i);
+			break;
+		}
+		w->screenr = r;
+		strcpy(buf, w->name);
+		wresize(w, i, m==Moved);
+		w->wctlready = 1;
+		if(Dx(r) > 0){
+			if(w != input)
+				wcurrent(w);
+		}else if(w == input)
+			wcurrent(nil);
+		flushimage(display, 1);
+		break;
+	case Refresh:
+		if(w->deleted || Dx(w->screenr)<=0 || !rectclip(&r, w->i->r))
+			break;
+		if(!w->mouseopen)
+			wrefresh(w, r);
+		flushimage(display, 1);
+		break;
+	case Movemouse:
+		if(sweeping || !ptinrect(r.min, w->i->r))
+			break;
+		wmovemouse(w, r.min);
+	case Rawon:
+		break;
+	case Rawoff:
+		if(w->deleted)
+			break;
+		while(w->nraw > 0){
+			wkeyctl(w, w->raw[0]);
+			--w->nraw;
+			runemove(w->raw, w->raw+1, w->nraw);
+		}
+		break;
+	case Holdon:
+	case Holdoff:
+		if(w->deleted)
+			break;
+		wrepaint(w);
+		flushimage(display, 1);
+		break;
+	case Deleted:
+		if(w->deleted)
+			break;
+		write(w->notefd, "hangup", 6);
+		wclosewin(w);
+		break;
+	case Exited:
+		frclear(&w->f, TRUE);
+		close(w->notefd);
+		chanfree(w->mc.c);
+		chanfree(w->ck);
+		chanfree(w->cctl);
+		chanfree(w->conswrite);
+		chanfree(w->consread);
+		chanfree(w->mouseread);
+		chanfree(w->wctlread);
+		free(w->raw);
+		free(w->r);
+		free(w->dir);
+		free(w->label);
+		free(w);
+		break;
+	}
+	return m;
+}
+
+/*
+ * Convert back to physical coordinates
+ */
+void
+wmovemouse(Window *w, Point p)
+{
+	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);
+}
+
+void
+wcurrent(Window *w)
+{
+	Window *oi;
+
+	if(wkeyboard!=nil && w==wkeyboard)
+		return;
+	oi = input;
+	input = w;
+	if(oi!=w && oi!=nil)
+		wrepaint(oi);
+	if(w !=nil){
+		wrepaint(w);
+		wsetcursor(w, 0);
+	}
+	if(w != oi){
+		if(oi){
+			oi->wctlready = 1;
+			wsendctlmesg(oi, Wakeup, ZR, nil);
+		}
+		if(w){
+			w->wctlready = 1;
+			wsendctlmesg(w, Wakeup, ZR, nil);
+		}
+	}
+}
+
+void
+wsetcursor(Window *w, int force)
+{
+	Cursor *p;
+
+	if(w==nil || /*w!=input || */ w->i==nil || Dx(w->screenr)<=0)
+		p = nil;
+	else if(wpointto(mouse->xy) == w){
+		p = w->cursorp;
+		if(p==nil && w->holding)
+			p = &whitearrow;
+	}else
+		p = nil;
+	if(!menuing)
+		riosetcursor(p, force && !menuing);
+}
+
+void
+riosetcursor(Cursor *p, int force)
+{
+	if(!force && p==lastcursor)
+		return;
+	setcursor(mousectl, p);
+	lastcursor = p;
+}
+
+Window*
+wtop(Point pt)
+{
+	Window *w;
+
+	w = wpointto(pt);
+	if(w){
+		if(w->topped == topped)
+			return nil;
+		topwindow(w->i);
+		wcurrent(w);
+		flushimage(display, 1);
+		w->topped = ++topped;
+	}
+	return w;
+}
+
+void
+wtopme(Window *w)
+{
+	if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
+		topwindow(w->i);
+		flushimage(display, 1);
+		w->topped = ++ topped;
+	}
+}
+
+void
+wbottomme(Window *w)
+{
+	if(w!=nil && w->i!=nil && !w->deleted){
+		bottomwindow(w->i);
+		flushimage(display, 1);
+		w->topped = 0;
+	}
+}
+
+Window*
+wlookid(int id)
+{
+	int i;
+
+	for(i=0; i<nwindow; i++)
+		if(window[i]->id == id)
+			return window[i];
+	return nil;
+}
+
+void
+wclosewin(Window *w)
+{
+	Rectangle r;
+	int i;
+
+	w->deleted = TRUE;
+	if(w == input){
+		input = nil;
+		wsetcursor(w, 0);
+	}
+	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*));
+			w->deleted = TRUE;
+			r = w->i->r;
+			/* move it off-screen to hide it, in case client is slow in letting it go */
+			MOVEIT originwindow(w->i, r.min, view->r.max);
+			freeimage(w->i);
+			w->i = nil;
+			return;
+		}
+	error("unknown window in closewin");
+}
+
+void
+wsetpid(Window *w, int pid, int dolabel)
+{
+	char buf[128];
+	int fd;
+
+	w->pid = pid;
+	if(dolabel){
+		sprint(buf, "rc %d", pid);
+		free(w->label);
+		w->label = estrdup(buf);
+	}
+	sprint(buf, "/proc/%d/notepg", pid);
+	fd = open(buf, OWRITE|OCEXEC);
+	if(w->notefd > 0)
+		close(w->notefd);
+	w->notefd = fd;
+}
+
+static Rune left1[] =  { '{', '[', '(', '<', 0xAB, 0 };
+static Rune right1[] = { '}', ']', ')', '>', 0xBB, 0 };
+static Rune left2[] =  { '\n', 0 };
+static Rune left3[] =  { '\'', '"', '`', 0 };
+
+Rune *left[] = {
+	left1,
+	left2,
+	left3,
+	nil
+};
+Rune *right[] = {
+	right1,
+	left2,
+	left3,
+	nil
+};
+
+void
+wdoubleclick(Window *w, 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 = 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 && isalnum(w->r[*q1]))
+		(*q1)++;
+	/* try filling out word to left */
+	while(*q0>0 && isalnum(w->r[*q0-1]))
+		(*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->f.nchars;
+	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+		wscrdraw(w);
+	else{
+		nl = 4*w->f.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->f.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->f.nchars){
+		frdelete(&w->f, 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->f.nchars){
+		n = w->org - org;
+		r = runemalloc(n);
+		runemove(r, w->r+org, n);
+		frinsert(&w->f, r, r+n, 0);
+		free(r);
+	}else
+		frdelete(&w->f, 0, w->f.nchars);
+	w->org = org;
+	wfill(w);
+	wscrdraw(w);
+	wsetselect(w, w->q0, w->q1);
+	if(fixup && w->f.p1 > w->f.p0)
+		frdrawsel(&w->f, frptofchar(&w->f, w->f.p1-1), w->f.p1-1, w->f.p1, 1);
+}
+
+void
+wsetselect(Window *w, uint q0, uint q1)
+{
+	int p0, p1;
+
+	/* w->f.p0 and w->f.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->f.nchars)
+		p0 = w->f.nchars;
+	if(p1 > w->f.nchars)
+		p1 = w->f.nchars;
+	if(p0==w->f.p0 && p1==w->f.p1)
+		return;
+	/* screen disagrees with desired selection */
+	if(w->f.p1<=p0 || p1<=w->f.p0 || p0==p1 || w->f.p1==w->f.p0){
+		/* no overlap or too easy to bother trying */
+		frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 0);
+		frdrawsel(&w->f, frptofchar(&w->f, p0), p0, p1, 1);
+		goto Return;
+	}
+	/* overlap; avoid unnecessary painting */
+	if(p0 < w->f.p0){
+		/* extend selection backwards */
+		frdrawsel(&w->f, frptofchar(&w->f, p0), p0, w->f.p0, 1);
+	}else if(p0 > w->f.p0){
+		/* trim first part of selection */
+		frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, p0, 0);
+	}
+	if(p1 > w->f.p1){
+		/* extend selection forwards */
+		frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, p1, 1);
+	}else if(p1 < w->f.p1){
+		/* trim last part of selection */
+		frdrawsel(&w->f, frptofchar(&w->f, p1), p1, w->f.p1, 0);
+	}
+
+    Return:
+	w->f.p0 = p0;
+	w->f.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->f.nchars)
+		frinsert(&w->f, r, r+n, q0-w->org);
+	return q0;
+}
+
+void
+wfill(Window *w)
+{
+	Rune *rp;
+	int i, n, m, nl;
+
+	if(w->f.lastlinefull)
+		return;
+	rp = malloc(messagesize);
+	do{
+		n = w->nr-(w->org+w->f.nchars);
+		if(n == 0)
+			break;
+		if(n > 2000)	/* educated guess at reasonable amount */
+			n = 2000;
+		runemove(rp, w->r+(w->org+w->f.nchars), n);
+		/*
+		 * it's expensive to frinsert more than we need, so
+		 * count newlines.
+		 */
+		nl = w->f.maxlines-w->f.nlines;
+		m = 0;
+		for(i=0; i<n; ){
+			if(rp[i++] == '\n'){
+				m++;
+				if(m >= nl)
+					break;
+			}
+		}
+		frinsert(&w->f, rp, rp+i, w->f.nchars);
+	}while(w->f.lastlinefull == FALSE);
+	free(rp);
+}
+
+char*
+wcontents(Window *w, int *ip)
+{
+	return runetobyte(w->r, w->nr, ip);
+}