| #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 <9pclient.h> | 
 | #include <plumb.h> | 
 | #include <complete.h> | 
 | #include "dat.h" | 
 | #include "fns.h" | 
 |  | 
 | #define MOVEIT if(0) | 
 |  | 
 | enum | 
 | { | 
 | 	HiWater	= 64000000,	/* 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; | 
 |  | 
 | static int | 
 | wscale(Window *w, int n) | 
 | { | 
 | 	if(w == nil || w->i == nil) | 
 | 		return n; | 
 | 	return scalesize(w->i->display, n); | 
 | } | 
 |  | 
 | 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, wscale(w, Selborder)+wscale(w, 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+wscale(w, Scrollwid); | 
 | 	w->lastsr = ZR; | 
 | 	r.min.x += wscale(w, Scrollwid)+wscale(w, 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, wscale(w, Selborder)); | 
 | 	draw(w->i, r, cols[BACK], nil, w->f.entire.min); | 
 | 	wborder(w, wscale(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, wscale(w, Selborder)+wscale(w, 1)); | 
 | 	w->scrollr = r; | 
 | 	w->scrollr.max.x = r.min.x+wscale(w, Scrollwid); | 
 | 	w->lastsr = ZR; | 
 | 	r.min.x += wscale(w, Scrollwid)+wscale(w, 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, wscale(w, Selborder)); | 
 | 		draw(w->i, r, cols[BACK], nil, w->f.entire.min); | 
 | 		wfill(w); | 
 | 		wsetselect(w, w->q0, w->q1); | 
 | 		wscrdraw(w); | 
 | 	} | 
 | 	wborder(w, wscale(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, wscale(w, Selborder)); | 
 | 	else | 
 | 		wborder(w, wscale(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, *up, *kbdr; | 
 | 	uint qh; | 
 | 	int nr, nb, c, wid, i, npart, initial, lastb, scrolling; | 
 | 	char *s, *t, part[UTFmax]; | 
 | 	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.m = w->mc.m; | 
 | 				m.counter = 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; | 
 | 			up = rp; | 
 | 			initial = 0; | 
 | 			for(i=0; i<nr; i++){ | 
 | 				switch(*bp){ | 
 | 				case 0: | 
 | 					break; | 
 | 				case '\b': | 
 | 					if(up == rp) | 
 | 						initial++; | 
 | 					else | 
 | 						--up; | 
 | 					break; | 
 | 				case '\r': | 
 | 					while(i<nr-1 && *(bp+1) == '\r'){ | 
 | 						bp++; | 
 | 						i++; | 
 | 					} | 
 | 					if(i<nr-1 && *(bp+1) != '\n'){ | 
 | 						while(up > rp && *(up-1) != '\n') | 
 | 							up--; | 
 | 						if(up == rp) | 
 | 							initial = wbswidth(w, '\r'); | 
 | 					}else if(i == nr-1) | 
 | 						*up++ = '\n'; | 
 | 					break; | 
 | 				default: | 
 | 					*up++ = *bp; | 
 | 					break; | 
 | 				} | 
 | 				bp++; | 
 | 			} | 
 | 			if(initial){ | 
 | 				if(initial > w->qh) | 
 | 					initial = w->qh; | 
 | 				qh = w->qh - initial; | 
 | 				wdelete(w, qh, qh+initial); | 
 | 				w->qh = qh; | 
 | 			} | 
 | 			nr = up - rp; | 
 | 			scrolling = w->scrolling && w->org <= w->qh && w->qh <= w->org + w->f.nchars; | 
 | 			w->qh = winsert(w, rp, nr, w->qh)+nr; | 
 | 			if(scrolling) | 
 | 				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; | 
 |  | 
 | 	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: | 
 | 			if(w->org > w->iq1) { | 
 | 				q0 = wbacknl(w, w->iq1, 1); | 
 | 				wsetorigin(w, q0, TRUE); | 
 | 			} else | 
 | 				wshow(w, 0); | 
 | 			return; | 
 | 		case Kend: | 
 | 			if(w->iq1 > w->org+w->f.nchars) { | 
 | 				q0 = wbacknl(w, w->iq1, 1); | 
 | 				wsetorigin(w, q0, TRUE); | 
 | 			} else | 
 | 				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 == Kcmd+'x'){ | 
 | 		wsnarf(w); | 
 | 		wcut(w); | 
 | 		wscrdraw(w); | 
 | 		return; | 
 | 	} | 
 | 	if(r == Kcmd+'c'){ | 
 | 		wsnarf(w); | 
 | 		return; | 
 | 	} | 
 | 	if(r == Kcmd+'v'){ | 
 | 		riogetsnarf(); | 
 | 		wpaste(w); | 
 | 		wscrdraw(w); | 
 | 		return; | 
 | 	} | 
 | 	if(r != 0x7F){ | 
 | 		wsnarf(w); | 
 | 		wcut(w); | 
 | 	} | 
 | 	switch(r){ | 
 | 	case 0x03:		/* maybe send interrupt */ | 
 | 		/* since ^C is so commonly used as interrupt, special case it */ | 
 | 		if (intrc() != 0x03) | 
 | 			break; | 
 | 		/* fall through */ | 
 | 	case 0x7F:		/* send interrupt */ | 
 | 		w->qh = w->nr; | 
 | 		wshow(w, w->qh); | 
 | 		winterrupt(w); | 
 | 		w->iq1 = w->q0; | 
 | 		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); | 
 | 		w->iq1 = w->q0; | 
 | 		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); | 
 | 		} | 
 | 		w->iq1 = w->q0; | 
 | 		return; | 
 | 	} | 
 | 	/* otherwise ordinary character; just insert */ | 
 | 	q0 = w->q0; | 
 | 	q0 = winsert(w, &r, 1, q0); | 
 | 	wshow(w, q0+1); | 
 | 	w->iq1 = w->q0; | 
 | } | 
 |  | 
 | 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); | 
 | 	} | 
 | 	if(w == input){ | 
 | 		wborder(w, wscale(w, Selborder)); | 
 | 		wsetcursor(w, 0); | 
 | 	}else | 
 | 		wborder(w, wscale(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 && c != '\r')	/* 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); | 
 | 	rioputsnarf(); | 
 | } | 
 |  | 
 | 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 && !w->holding && 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 CFid *fd; | 
 | 	char buf[32]; | 
 | 	uint p0, p1; | 
 | 	Cursor *c; | 
 |  | 
 | 	if(fd == nil) | 
 | 		fd = plumbopenfid("send", OWRITE); | 
 | 	if(fd == nil) | 
 | 		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(plumbsendtofid(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, wscale(w, 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->iq1) | 
 | 		w->iq1 -= min(n, w->iq1-q0); | 
 | 	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; | 
 | 					riogetsnarf(); | 
 | 				} | 
 | 				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]; | 
 |  | 
 | 	w->pid = pid; | 
 | 	if(dolabel){ | 
 | 		sprint(buf, "rc %d", pid); | 
 | 		free(w->label); | 
 | 		w->label = estrdup(buf); | 
 | 		drawsetlabel(w->label); | 
 | 	} | 
 | } | 
 |  | 
 | static Rune left1[] =  {  | 
 | 	'{', '[', '(', '<', 0xAB, | 
 | 	0x207d, 0x2329, 0x27e6, 0x27e8, 0x27ea,  | 
 | 	0xfe59, 0xfe5b, 0xfe5d, 0xff08, 0xff3b, 0xff5b,  | 
 | 	0 | 
 | }; | 
 | static Rune right1[] = { | 
 | 	'}', ']', ')', '>', 0xBB, | 
 | 	0x207e, 0x232a, 0x27e7, 0x27e9, 0x27eb,  | 
 | 	0xfe5a, 0xfe5c, 0xfe5e, 0xff09, 0xff3d, 0xff5d, | 
 | 	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->iq1) | 
 | 		w->iq1 += 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); | 
 | } |