| #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 "dat.h" | 
 | #include "fns.h" | 
 |  | 
 | enum | 
 | { | 
 | 	Ctlsize	= 5*12 | 
 | }; | 
 |  | 
 | char	Edel[]		= "deleted window"; | 
 | char	Ebadctl[]		= "ill-formed control message"; | 
 | char	Ebadaddr[]	= "bad address syntax"; | 
 | char	Eaddr[]		= "address out of range"; | 
 | char	Einuse[]		= "already in use"; | 
 | char	Ebadevent[]	= "bad event syntax"; | 
 | extern char Eperm[]; | 
 |  | 
 | static | 
 | void | 
 | clampaddr(Window *w) | 
 | { | 
 | 	if(w->addr.q0 < 0) | 
 | 		w->addr.q0 = 0; | 
 | 	if(w->addr.q1 < 0) | 
 | 		w->addr.q1 = 0; | 
 | 	if(w->addr.q0 > w->body.file->b.nc) | 
 | 		w->addr.q0 = w->body.file->b.nc; | 
 | 	if(w->addr.q1 > w->body.file->b.nc) | 
 | 		w->addr.q1 = w->body.file->b.nc; | 
 | } | 
 |  | 
 | void | 
 | xfidctl(void *arg) | 
 | { | 
 | 	Xfid *x; | 
 | 	void (*f)(Xfid*); | 
 |  | 
 | 	threadsetname("xfidctlthread"); | 
 | 	x = arg; | 
 | 	for(;;){ | 
 | 		f = (void(*)(Xfid*))recvp(x->c); | 
 | 		(*f)(x); | 
 | 		flushimage(display, 1); | 
 | 		sendp(cxfidfree, x); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | xfidflush(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	int i, j; | 
 | 	Window *w; | 
 | 	Column *c; | 
 | 	Xfid *wx; | 
 |  | 
 | 	xfidlogflush(x); | 
 |  | 
 | 	/* search windows for matching tag */ | 
 | 	qlock(&row.lk); | 
 | 	for(j=0; j<row.ncol; j++){ | 
 | 		c = row.col[j]; | 
 | 		for(i=0; i<c->nw; i++){ | 
 | 			w = c->w[i]; | 
 | 			winlock(w, 'E'); | 
 | 			wx = w->eventx; | 
 | 			if(wx!=nil && wx->fcall.tag==x->fcall.oldtag){ | 
 | 				w->eventx = nil; | 
 | 				wx->flushed = TRUE; | 
 | 				sendp(wx->c, nil); | 
 | 				winunlock(w); | 
 | 				goto out; | 
 | 			} | 
 | 			winunlock(w); | 
 | 		} | 
 | 	} | 
 | out: | 
 | 	qunlock(&row.lk); | 
 | 	respond(x, &fc, nil); | 
 | } | 
 |  | 
 | void | 
 | xfidopen(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	Window *w; | 
 | 	Text *t; | 
 | 	char *s; | 
 | 	Rune *r; | 
 | 	int m, n, q, q0, q1; | 
 |  | 
 | 	w = x->f->w; | 
 | 	t = &w->body; | 
 | 	q = FILE(x->f->qid); | 
 | 	if(w){ | 
 | 		winlock(w, 'E'); | 
 | 		switch(q){ | 
 | 		case QWaddr: | 
 | 			if(w->nopen[q]++ == 0){ | 
 | 				w->addr = range(0, 0); | 
 | 				w->limit = range(-1,-1); | 
 | 			} | 
 | 			break; | 
 | 		case QWdata: | 
 | 		case QWxdata: | 
 | 			w->nopen[q]++; | 
 | 			break; | 
 | 		case QWevent: | 
 | 			if(w->nopen[q]++ == 0){ | 
 | 				if(!w->isdir && w->col!=nil){ | 
 | 					w->filemenu = FALSE; | 
 | 					winsettag(w); | 
 | 				} | 
 | 			} | 
 | 			break; | 
 | 		case QWrdsel: | 
 | 			/* | 
 | 			 * Use a temporary file. | 
 | 			 * A pipe would be the obvious, but we can't afford the | 
 | 			 * broken pipe notification.  Using the code to read QWbody | 
 | 			 * is n², which should probably also be fixed.  Even then, | 
 | 			 * though, we'd need to squirrel away the data in case it's | 
 | 			 * modified during the operation, e.g. by |sort | 
 | 			 */ | 
 | 			if(w->rdselfd > 0){ | 
 | 				winunlock(w); | 
 | 				respond(x, &fc, Einuse); | 
 | 				return; | 
 | 			} | 
 | 			w->rdselfd = tempfile(); | 
 | 			if(w->rdselfd < 0){ | 
 | 				winunlock(w); | 
 | 				respond(x, &fc, "can't create temp file"); | 
 | 				return; | 
 | 			} | 
 | 			w->nopen[q]++; | 
 | 			q0 = t->q0; | 
 | 			q1 = t->q1; | 
 | 			r = fbufalloc(); | 
 | 			s = fbufalloc(); | 
 | 			while(q0 < q1){ | 
 | 				n = q1 - q0; | 
 | 				if(n > BUFSIZE/UTFmax) | 
 | 					n = BUFSIZE/UTFmax; | 
 | 				bufread(&t->file->b, q0, r, n); | 
 | 				m = snprint(s, BUFSIZE+1, "%.*S", n, r); | 
 | 				if(write(w->rdselfd, s, m) != m){ | 
 | 					warning(nil, "can't write temp file for pipe command %r\n"); | 
 | 					break; | 
 | 				} | 
 | 				q0 += n; | 
 | 			} | 
 | 			fbuffree(s); | 
 | 			fbuffree(r); | 
 | 			break; | 
 | 		case QWwrsel: | 
 | 			w->nopen[q]++; | 
 | 			seq++; | 
 | 			filemark(t->file); | 
 | 			cut(t, t, nil, FALSE, TRUE, nil, 0); | 
 | 			w->wrselrange = range(t->q1, t->q1); | 
 | 			w->nomark = TRUE; | 
 | 			break; | 
 | 		case QWeditout: | 
 | 			if(editing == FALSE){ | 
 | 				winunlock(w); | 
 | 				respond(x, &fc, Eperm); | 
 | 				return; | 
 | 			} | 
 | 			if(!canqlock(&w->editoutlk)){ | 
 | 				winunlock(w); | 
 | 				respond(x, &fc, Einuse); | 
 | 				return; | 
 | 			} | 
 | 			w->wrselrange = range(t->q1, t->q1); | 
 | 			break; | 
 | 		} | 
 | 		winunlock(w); | 
 | 	} | 
 | 	else{ | 
 | 		switch(q){ | 
 | 		case Qlog: | 
 | 			xfidlogopen(x); | 
 | 			break; | 
 | 		case Qeditout: | 
 | 			if(!canqlock(&editoutlk)){ | 
 | 				respond(x, &fc, Einuse); | 
 | 				return; | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	fc.qid = x->f->qid; | 
 | 	fc.iounit = messagesize-IOHDRSZ; | 
 | 	x->f->open = TRUE; | 
 | 	respond(x, &fc, nil); | 
 | } | 
 |  | 
 | void | 
 | xfidclose(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	Window *w; | 
 | 	int q; | 
 | 	Text *t; | 
 |  | 
 | 	w = x->f->w; | 
 | 	x->f->busy = FALSE; | 
 | 	x->f->w = nil; | 
 | 	if(x->f->open == FALSE){ | 
 | 		if(w != nil) | 
 | 			winclose(w); | 
 | 		respond(x, &fc, nil); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	q = FILE(x->f->qid); | 
 | 	x->f->open = FALSE; | 
 | 	if(w){ | 
 | 		winlock(w, 'E'); | 
 | 		switch(q){ | 
 | 		case QWctl: | 
 | 			if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){ | 
 | 				w->ctlfid = ~0; | 
 | 				qunlock(&w->ctllock); | 
 | 			} | 
 | 			break; | 
 | 		case QWdata: | 
 | 		case QWxdata: | 
 | 			w->nomark = FALSE; | 
 | 			/* fall through */ | 
 | 		case QWaddr: | 
 | 		case QWevent:	/* BUG: do we need to shut down Xfid? */ | 
 | 			if(--w->nopen[q] == 0){ | 
 | 				if(q == QWdata || q == QWxdata) | 
 | 					w->nomark = FALSE; | 
 | 				if(q==QWevent && !w->isdir && w->col!=nil){ | 
 | 					w->filemenu = TRUE; | 
 | 					winsettag(w); | 
 | 				} | 
 | 				if(q == QWevent){ | 
 | 					free(w->dumpstr); | 
 | 					free(w->dumpdir); | 
 | 					w->dumpstr = nil; | 
 | 					w->dumpdir = nil; | 
 | 				} | 
 | 			} | 
 | 			break; | 
 | 		case QWrdsel: | 
 | 			close(w->rdselfd); | 
 | 			w->rdselfd = 0; | 
 | 			break; | 
 | 		case QWwrsel: | 
 | 			w->nomark = FALSE; | 
 | 			t = &w->body; | 
 | 			/* before: only did this if !w->noscroll, but that didn't seem right in practice */ | 
 | 			textshow(t, min(w->wrselrange.q0, t->file->b.nc), | 
 | 				min(w->wrselrange.q1, t->file->b.nc), 1); | 
 | 			textscrdraw(t); | 
 | 			break; | 
 | 		case QWeditout: | 
 | 			qunlock(&w->editoutlk); | 
 | 			break; | 
 | 		} | 
 | 		winunlock(w); | 
 | 		winclose(w); | 
 | 	} | 
 | 	else{ | 
 | 		switch(q){ | 
 | 		case Qeditout: | 
 | 			qunlock(&editoutlk); | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	respond(x, &fc, nil); | 
 | } | 
 |  | 
 | void | 
 | xfidread(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	int n, q; | 
 | 	uint off; | 
 | 	char *b; | 
 | 	char buf[256]; | 
 | 	Window *w; | 
 |  | 
 | 	q = FILE(x->f->qid); | 
 | 	w = x->f->w; | 
 | 	if(w == nil){ | 
 | 		fc.count = 0; | 
 | 		switch(q){ | 
 | 		case Qcons: | 
 | 		case Qlabel: | 
 | 			break; | 
 | 		case Qindex: | 
 | 			xfidindexread(x); | 
 | 			return; | 
 | 		case Qlog: | 
 | 			xfidlogread(x); | 
 | 			return; | 
 | 		default: | 
 | 			warning(nil, "unknown qid %d\n", q); | 
 | 			break; | 
 | 		} | 
 | 		respond(x, &fc, nil); | 
 | 		return; | 
 | 	} | 
 | 	winlock(w, 'F'); | 
 | 	if(w->col == nil){ | 
 | 		winunlock(w); | 
 | 		respond(x, &fc, Edel); | 
 | 		return; | 
 | 	} | 
 | 	off = x->fcall.offset; | 
 | 	switch(q){ | 
 | 	case QWaddr: | 
 | 		textcommit(&w->body, TRUE); | 
 | 		clampaddr(w); | 
 | 		sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1); | 
 | 		goto Readbuf; | 
 |  | 
 | 	case QWbody: | 
 | 		xfidutfread(x, &w->body, w->body.file->b.nc, QWbody); | 
 | 		break; | 
 |  | 
 | 	case QWctl: | 
 | 		b = winctlprint(w, buf, 1); | 
 | 		goto Readb; | 
 |  | 
 | 	Readbuf: | 
 | 		b = buf; | 
 | 	Readb: | 
 | 		n = strlen(b); | 
 | 		if(off > n) | 
 | 			off = n; | 
 | 		if(off+x->fcall.count > n) | 
 | 			x->fcall.count = n-off; | 
 | 		fc.count = x->fcall.count; | 
 | 		fc.data = b+off; | 
 | 		respond(x, &fc, nil); | 
 | 		if(b != buf) | 
 | 			free(b); | 
 | 		break; | 
 |  | 
 | 	case QWevent: | 
 | 		xfideventread(x, w); | 
 | 		break; | 
 |  | 
 | 	case QWdata: | 
 | 		/* BUG: what should happen if q1 > q0? */ | 
 | 		if(w->addr.q0 > w->body.file->b.nc){ | 
 | 			respond(x, &fc, Eaddr); | 
 | 			break; | 
 | 		} | 
 | 		w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc); | 
 | 		w->addr.q1 = w->addr.q0; | 
 | 		break; | 
 |  | 
 | 	case QWxdata: | 
 | 		/* BUG: what should happen if q1 > q0? */ | 
 | 		if(w->addr.q0 > w->body.file->b.nc){ | 
 | 			respond(x, &fc, Eaddr); | 
 | 			break; | 
 | 		} | 
 | 		w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->addr.q1); | 
 | 		break; | 
 |  | 
 | 	case QWtag: | 
 | 		xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag); | 
 | 		break; | 
 |  | 
 | 	case QWrdsel: | 
 | 		seek(w->rdselfd, off, 0); | 
 | 		n = x->fcall.count; | 
 | 		if(n > BUFSIZE) | 
 | 			n = BUFSIZE; | 
 | 		b = fbufalloc(); | 
 | 		n = read(w->rdselfd, b, n); | 
 | 		if(n < 0){ | 
 | 			respond(x, &fc, "I/O error in temp file"); | 
 | 			break; | 
 | 		} | 
 | 		fc.count = n; | 
 | 		fc.data = b; | 
 | 		respond(x, &fc, nil); | 
 | 		fbuffree(b); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		sprint(buf, "unknown qid %d in read", q); | 
 | 		respond(x, &fc, nil); | 
 | 	} | 
 | 	winunlock(w); | 
 | } | 
 |  | 
 | static int | 
 | shouldscroll(Text *t, uint q0, int qid) | 
 | { | 
 | 	if(qid == Qcons) | 
 | 		return TRUE; | 
 | 	return t->org <= q0 && q0 <= t->org+t->fr.nchars; | 
 | } | 
 |  | 
 | static Rune* | 
 | fullrunewrite(Xfid *x, int *inr) | 
 | { | 
 | 	int q, cnt, c, nb, nr; | 
 | 	Rune *r; | 
 |  | 
 | 	q = x->f->nrpart; | 
 | 	cnt = x->fcall.count; | 
 | 	if(q > 0){ | 
 | 		memmove(x->fcall.data+q, x->fcall.data, cnt);	/* there's room; see fsysproc */ | 
 | 		memmove(x->fcall.data, x->f->rpart, q); | 
 | 		cnt += q; | 
 | 		x->f->nrpart = 0; | 
 | 	} | 
 | 	r = runemalloc(cnt); | 
 | 	cvttorunes(x->fcall.data, cnt-UTFmax, r, &nb, &nr, nil); | 
 | 	/* approach end of buffer */ | 
 | 	while(fullrune(x->fcall.data+nb, cnt-nb)){ | 
 | 		c = nb; | 
 | 		nb += chartorune(&r[nr], x->fcall.data+c); | 
 | 		if(r[nr]) | 
 | 			nr++; | 
 | 	} | 
 | 	if(nb < cnt){ | 
 | 		memmove(x->f->rpart, x->fcall.data+nb, cnt-nb); | 
 | 		x->f->nrpart = cnt-nb; | 
 | 	} | 
 | 	*inr = nr; | 
 | 	return r; | 
 | } | 
 |  | 
 | void | 
 | xfidwrite(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	int c, qid, nb, nr, eval; | 
 | 	char buf[64], *err; | 
 | 	Window *w; | 
 | 	Rune *r; | 
 | 	Range a; | 
 | 	Text *t; | 
 | 	uint q0, tq0, tq1; | 
 |  | 
 | 	qid = FILE(x->f->qid); | 
 | 	w = x->f->w; | 
 | 	if(w){ | 
 | 		c = 'F'; | 
 | 		if(qid==QWtag || qid==QWbody) | 
 | 			c = 'E'; | 
 | 		winlock(w, c); | 
 | 		if(w->col == nil){ | 
 | 			winunlock(w); | 
 | 			respond(x, &fc, Edel); | 
 | 			return; | 
 | 		} | 
 | 	} | 
 | 	x->fcall.data[x->fcall.count] = 0; | 
 | 	switch(qid){ | 
 | 	case Qcons: | 
 | 		w = errorwin(x->f->mntdir, 'X'); | 
 | 		t=&w->body; | 
 | 		goto BodyTag; | 
 |  | 
 | 	case Qlabel: | 
 | 		fc.count = x->fcall.count; | 
 | 		respond(x, &fc, nil); | 
 | 		break; | 
 |  | 
 | 	case QWaddr: | 
 | 		x->fcall.data[x->fcall.count] = 0; | 
 | 		r = bytetorune(x->fcall.data, &nr); | 
 | 		t = &w->body; | 
 | 		wincommit(w, t); | 
 | 		eval = TRUE; | 
 | 		a = address(FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb); | 
 | 		free(r); | 
 | 		if(nb < nr){ | 
 | 			respond(x, &fc, Ebadaddr); | 
 | 			break; | 
 | 		} | 
 | 		if(!eval){ | 
 | 			respond(x, &fc, Eaddr); | 
 | 			break; | 
 | 		} | 
 | 		w->addr = a; | 
 | 		fc.count = x->fcall.count; | 
 | 		respond(x, &fc, nil); | 
 | 		break; | 
 |  | 
 | 	case Qeditout: | 
 | 	case QWeditout: | 
 | 		r = fullrunewrite(x, &nr); | 
 | 		if(w) | 
 | 			err = edittext(w, w->wrselrange.q1, r, nr); | 
 | 		else | 
 | 			err = edittext(nil, 0, r, nr); | 
 | 		free(r); | 
 | 		if(err != nil){ | 
 | 			respond(x, &fc, err); | 
 | 			break; | 
 | 		} | 
 | 		fc.count = x->fcall.count; | 
 | 		respond(x, &fc, nil); | 
 | 		break; | 
 |  | 
 | 	case QWerrors: | 
 | 		w = errorwinforwin(w); | 
 | 		t = &w->body; | 
 | 		goto BodyTag; | 
 |  | 
 | 	case QWbody: | 
 | 	case QWwrsel: | 
 | 		t = &w->body; | 
 | 		goto BodyTag; | 
 |  | 
 | 	case QWctl: | 
 | 		xfidctlwrite(x, w); | 
 | 		break; | 
 |  | 
 | 	case QWdata: | 
 | 		a = w->addr; | 
 | 		t = &w->body; | 
 | 		wincommit(w, t); | 
 | 		if(a.q0>t->file->b.nc || a.q1>t->file->b.nc){ | 
 | 			respond(x, &fc, Eaddr); | 
 | 			break; | 
 | 		} | 
 | 		r = runemalloc(x->fcall.count); | 
 | 		cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil); | 
 | 		if(w->nomark == FALSE){ | 
 | 			seq++; | 
 | 			filemark(t->file); | 
 | 		} | 
 | 		q0 = a.q0; | 
 | 		if(a.q1 > q0){ | 
 | 			textdelete(t, q0, a.q1, TRUE); | 
 | 			w->addr.q1 = q0; | 
 | 		} | 
 | 		tq0 = t->q0; | 
 | 		tq1 = t->q1; | 
 | 		textinsert(t, q0, r, nr, TRUE); | 
 | 		if(tq0 >= q0) | 
 | 			tq0 += nr; | 
 | 		if(tq1 >= q0) | 
 | 			tq1 += nr; | 
 | 		textsetselect(t, tq0, tq1); | 
 | 		if(shouldscroll(t, q0, qid)) | 
 | 			textshow(t, q0+nr, q0+nr, 0); | 
 | 		textscrdraw(t); | 
 | 		winsettag(w); | 
 | 		free(r); | 
 | 		w->addr.q0 += nr; | 
 | 		w->addr.q1 = w->addr.q0; | 
 | 		fc.count = x->fcall.count; | 
 | 		respond(x, &fc, nil); | 
 | 		break; | 
 |  | 
 | 	case QWevent: | 
 | 		xfideventwrite(x, w); | 
 | 		break; | 
 |  | 
 | 	case QWtag: | 
 | 		t = &w->tag; | 
 | 		goto BodyTag; | 
 |  | 
 | 	BodyTag: | 
 | 		r = fullrunewrite(x, &nr); | 
 | 		if(nr > 0){ | 
 | 			wincommit(w, t); | 
 | 			if(qid == QWwrsel){ | 
 | 				q0 = w->wrselrange.q1; | 
 | 				if(q0 > t->file->b.nc) | 
 | 					q0 = t->file->b.nc; | 
 | 			}else | 
 | 				q0 = t->file->b.nc; | 
 | 			if(qid == QWtag) | 
 | 				textinsert(t, q0, r, nr, TRUE); | 
 | 			else{ | 
 | 				if(w->nomark == FALSE){ | 
 | 					seq++; | 
 | 					filemark(t->file); | 
 | 				} | 
 | 				q0 = textbsinsert(t, q0, r, nr, TRUE, &nr); | 
 | 				textsetselect(t, t->q0, t->q1);	/* insert could leave it somewhere else */ | 
 | 				if(qid!=QWwrsel && shouldscroll(t, q0, qid)) | 
 | 					textshow(t, q0+nr, q0+nr, 1); | 
 | 				textscrdraw(t); | 
 | 			} | 
 | 			winsettag(w); | 
 | 			if(qid == QWwrsel) | 
 | 				w->wrselrange.q1 += nr; | 
 | 			free(r); | 
 | 		} | 
 | 		fc.count = x->fcall.count; | 
 | 		respond(x, &fc, nil); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		sprint(buf, "unknown qid %d in write", qid); | 
 | 		respond(x, &fc, buf); | 
 | 		break; | 
 | 	} | 
 | 	if(w) | 
 | 		winunlock(w); | 
 | } | 
 |  | 
 | void | 
 | xfidctlwrite(Xfid *x, Window *w) | 
 | { | 
 | 	Fcall fc; | 
 | 	int i, m, n, nb, nr, nulls; | 
 | 	Rune *r; | 
 | 	char *err, *p, *pp, *q, *e; | 
 | 	int isfbuf, scrdraw, settag; | 
 | 	Text *t; | 
 |  | 
 | 	err = nil; | 
 | 	e = x->fcall.data+x->fcall.count; | 
 | 	scrdraw = FALSE; | 
 | 	settag = FALSE; | 
 | 	isfbuf = TRUE; | 
 | 	if(x->fcall.count < RBUFSIZE) | 
 | 		r = fbufalloc(); | 
 | 	else{ | 
 | 		isfbuf = FALSE; | 
 | 		r = emalloc(x->fcall.count*UTFmax+1); | 
 | 	} | 
 | 	x->fcall.data[x->fcall.count] = 0; | 
 | 	textcommit(&w->tag, TRUE); | 
 | 	for(n=0; n<x->fcall.count; n+=m){ | 
 | 		p = x->fcall.data+n; | 
 | 		if(strncmp(p, "lock", 4) == 0){	/* make window exclusive use */ | 
 | 			qlock(&w->ctllock); | 
 | 			w->ctlfid = x->f->fid; | 
 | 			m = 4; | 
 | 		}else | 
 | 		if(strncmp(p, "unlock", 6) == 0){	/* release exclusive use */ | 
 | 			w->ctlfid = ~0; | 
 | 			qunlock(&w->ctllock); | 
 | 			m = 6; | 
 | 		}else | 
 | 		if(strncmp(p, "clean", 5) == 0){	/* mark window 'clean', seq=0 */ | 
 | 			t = &w->body; | 
 | 			t->eq0 = ~0; | 
 | 			filereset(t->file); | 
 | 			t->file->mod = FALSE; | 
 | 			w->dirty = FALSE; | 
 | 			settag = TRUE; | 
 | 			m = 5; | 
 | 		}else | 
 | 		if(strncmp(p, "dirty", 5) == 0){	/* mark window 'dirty' */ | 
 | 			t = &w->body; | 
 | 			/* doesn't change sequence number, so "Put" won't appear.  it shouldn't. */ | 
 | 			t->file->mod = TRUE; | 
 | 			w->dirty = TRUE; | 
 | 			settag = TRUE; | 
 | 			m = 5; | 
 | 		}else | 
 | 		if(strncmp(p, "show", 4) == 0){	/* show dot */ | 
 | 			t = &w->body; | 
 | 			textshow(t, t->q0, t->q1, 1); | 
 | 			m = 4; | 
 | 		}else | 
 | 		if(strncmp(p, "name ", 5) == 0){	/* set file name */ | 
 | 			pp = p+5; | 
 | 			m = 5; | 
 | 			q = memchr(pp, '\n', e-pp); | 
 | 			if(q==nil || q==pp){ | 
 | 				err = Ebadctl; | 
 | 				break; | 
 | 			} | 
 | 			*q = 0; | 
 | 			nulls = FALSE; | 
 | 			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); | 
 | 			if(nulls){ | 
 | 				err = "nulls in file name"; | 
 | 				break; | 
 | 			} | 
 | 			for(i=0; i<nr; i++) | 
 | 				if(r[i] <= ' '){ | 
 | 					err = "bad character in file name"; | 
 | 					goto out; | 
 | 				} | 
 | out: | 
 | 			seq++; | 
 | 			filemark(w->body.file); | 
 | 			winsetname(w, r, nr); | 
 | 			m += (q+1) - pp; | 
 | 		}else | 
 | 		if(strncmp(p, "dump ", 5) == 0){	/* set dump string */ | 
 | 			pp = p+5; | 
 | 			m = 5; | 
 | 			q = memchr(pp, '\n', e-pp); | 
 | 			if(q==nil || q==pp){ | 
 | 				err = Ebadctl; | 
 | 				break; | 
 | 			} | 
 | 			*q = 0; | 
 | 			nulls = FALSE; | 
 | 			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); | 
 | 			if(nulls){ | 
 | 				err = "nulls in dump string"; | 
 | 				break; | 
 | 			} | 
 | 			w->dumpstr = runetobyte(r, nr); | 
 | 			m += (q+1) - pp; | 
 | 		}else | 
 | 		if(strncmp(p, "dumpdir ", 8) == 0){	/* set dump directory */ | 
 | 			pp = p+8; | 
 | 			m = 8; | 
 | 			q = memchr(pp, '\n', e-pp); | 
 | 			if(q==nil || q==pp){ | 
 | 				err = Ebadctl; | 
 | 				break; | 
 | 			} | 
 | 			*q = 0; | 
 | 			nulls = FALSE; | 
 | 			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); | 
 | 			if(nulls){ | 
 | 				err = "nulls in dump directory string"; | 
 | 				break; | 
 | 			} | 
 | 			w->dumpdir = runetobyte(r, nr); | 
 | 			m += (q+1) - pp; | 
 | 		}else | 
 | 		if(strncmp(p, "delete", 6) == 0){	/* delete for sure */ | 
 | 			colclose(w->col, w, TRUE); | 
 | 			m = 6; | 
 | 		}else | 
 | 		if(strncmp(p, "del", 3) == 0){	/* delete, but check dirty */ | 
 | 			if(!winclean(w, TRUE)){ | 
 | 				err = "file dirty"; | 
 | 				break; | 
 | 			} | 
 | 			colclose(w->col, w, TRUE); | 
 | 			m = 3; | 
 | 		}else | 
 | 		if(strncmp(p, "get", 3) == 0){	/* get file */ | 
 | 			get(&w->body, nil, nil, FALSE, XXX, nil, 0); | 
 | 			m = 3; | 
 | 		}else | 
 | 		if(strncmp(p, "put", 3) == 0){	/* put file */ | 
 | 			put(&w->body, nil, nil, XXX, XXX, nil, 0); | 
 | 			m = 3; | 
 | 		}else | 
 | 		if(strncmp(p, "dot=addr", 8) == 0){	/* set dot */ | 
 | 			textcommit(&w->body, TRUE); | 
 | 			clampaddr(w); | 
 | 			w->body.q0 = w->addr.q0; | 
 | 			w->body.q1 = w->addr.q1; | 
 | 			textsetselect(&w->body, w->body.q0, w->body.q1); | 
 | 			settag = TRUE; | 
 | 			m = 8; | 
 | 		}else | 
 | 		if(strncmp(p, "addr=dot", 8) == 0){	/* set addr */ | 
 | 			w->addr.q0 = w->body.q0; | 
 | 			w->addr.q1 = w->body.q1; | 
 | 			m = 8; | 
 | 		}else | 
 | 		if(strncmp(p, "limit=addr", 10) == 0){	/* set limit */ | 
 | 			textcommit(&w->body, TRUE); | 
 | 			clampaddr(w); | 
 | 			w->limit.q0 = w->addr.q0; | 
 | 			w->limit.q1 = w->addr.q1; | 
 | 			m = 10; | 
 | 		}else | 
 | 		if(strncmp(p, "nomark", 6) == 0){	/* turn off automatic marking */ | 
 | 			w->nomark = TRUE; | 
 | 			m = 6; | 
 | 		}else | 
 | 		if(strncmp(p, "mark", 4) == 0){	/* mark file */ | 
 | 			seq++; | 
 | 			filemark(w->body.file); | 
 | 			settag = TRUE; | 
 | 			m = 4; | 
 | 		}else | 
 | 		if(strncmp(p, "nomenu", 6) == 0){	/* turn off automatic menu */ | 
 | 			w->filemenu = FALSE; | 
 | 			m = 6; | 
 | 		}else | 
 | 		if(strncmp(p, "menu", 4) == 0){	/* enable automatic menu */ | 
 | 			w->filemenu = TRUE; | 
 | 			m = 4; | 
 | 		}else | 
 | 		if(strncmp(p, "cleartag", 8) == 0){	/* wipe tag right of bar */ | 
 | 			wincleartag(w); | 
 | 			settag = TRUE; | 
 | 			m = 8; | 
 | 		}else{ | 
 | 			err = Ebadctl; | 
 | 			break; | 
 | 		} | 
 | 		while(p[m] == '\n') | 
 | 			m++; | 
 | 	} | 
 |  | 
 | 	if(isfbuf) | 
 | 		fbuffree(r); | 
 | 	else | 
 | 		free(r); | 
 | 	if(err) | 
 | 		n = 0; | 
 | 	fc.count = n; | 
 | 	respond(x, &fc, err); | 
 | 	if(settag) | 
 | 		winsettag(w); | 
 | 	if(scrdraw) | 
 | 		textscrdraw(&w->body); | 
 | } | 
 |  | 
 | void | 
 | xfideventwrite(Xfid *x, Window *w) | 
 | { | 
 | 	Fcall fc; | 
 | 	int m, n; | 
 | 	Rune *r; | 
 | 	char *err, *p, *q; | 
 | 	int isfbuf; | 
 | 	Text *t; | 
 | 	int c; | 
 | 	uint q0, q1; | 
 |  | 
 | 	err = nil; | 
 | 	isfbuf = TRUE; | 
 | 	if(x->fcall.count < RBUFSIZE) | 
 | 		r = fbufalloc(); | 
 | 	else{ | 
 | 		isfbuf = FALSE; | 
 | 		r = emalloc(x->fcall.count*UTFmax+1); | 
 | 	} | 
 | 	for(n=0; n<x->fcall.count; n+=m){ | 
 | 		p = x->fcall.data+n; | 
 | 		w->owner = *p++;	/* disgusting */ | 
 | 		c = *p++; | 
 | 		while(*p == ' ') | 
 | 			p++; | 
 | 		q0 = strtoul(p, &q, 10); | 
 | 		if(q == p) | 
 | 			goto Rescue; | 
 | 		p = q; | 
 | 		while(*p == ' ') | 
 | 			p++; | 
 | 		q1 = strtoul(p, &q, 10); | 
 | 		if(q == p) | 
 | 			goto Rescue; | 
 | 		p = q; | 
 | 		while(*p == ' ') | 
 | 			p++; | 
 | 		if(*p++ != '\n') | 
 | 			goto Rescue; | 
 | 		m = p-(x->fcall.data+n); | 
 | 		if('a'<=c && c<='z') | 
 | 			t = &w->tag; | 
 | 		else if('A'<=c && c<='Z') | 
 | 			t = &w->body; | 
 | 		else | 
 | 			goto Rescue; | 
 | 		if(q0>t->file->b.nc || q1>t->file->b.nc || q0>q1) | 
 | 			goto Rescue; | 
 |  | 
 | 		qlock(&row.lk);	/* just like mousethread */ | 
 | 		switch(c){ | 
 | 		case 'x': | 
 | 		case 'X': | 
 | 			execute(t, q0, q1, TRUE, nil); | 
 | 			break; | 
 | 		case 'l': | 
 | 		case 'L': | 
 | 			look3(t, q0, q1, TRUE); | 
 | 			break; | 
 | 		default: | 
 | 			qunlock(&row.lk); | 
 | 			goto Rescue; | 
 | 		} | 
 | 		qunlock(&row.lk); | 
 |  | 
 | 	} | 
 |  | 
 |     Out: | 
 | 	if(isfbuf) | 
 | 		fbuffree(r); | 
 | 	else | 
 | 		free(r); | 
 | 	if(err) | 
 | 		n = 0; | 
 | 	fc.count = n; | 
 | 	respond(x, &fc, err); | 
 | 	return; | 
 |  | 
 |     Rescue: | 
 | 	err = Ebadevent; | 
 | 	goto Out; | 
 | } | 
 |  | 
 | void | 
 | xfidutfread(Xfid *x, Text *t, uint q1, int qid) | 
 | { | 
 | 	Fcall fc; | 
 | 	Window *w; | 
 | 	Rune *r; | 
 | 	char *b, *b1; | 
 | 	uint q, off, boff; | 
 | 	int m, n, nr, nb; | 
 |  | 
 | 	w = t->w; | 
 | 	wincommit(w, t); | 
 | 	off = x->fcall.offset; | 
 | 	r = fbufalloc(); | 
 | 	b = fbufalloc(); | 
 | 	b1 = fbufalloc(); | 
 | 	n = 0; | 
 | 	if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){ | 
 | 		boff = w->utflastboff; | 
 | 		q = w->utflastq; | 
 | 	}else{ | 
 | 		/* BUG: stupid code: scan from beginning */ | 
 | 		boff = 0; | 
 | 		q = 0; | 
 | 	} | 
 | 	w->utflastqid = qid; | 
 | 	while(q<q1 && n<x->fcall.count){ | 
 | 		/* | 
 | 		 * Updating here avoids partial rune problem: we're always on a | 
 | 		 * char boundary. The cost is we will usually do one more read | 
 | 		 * than we really need, but that's better than being n^2. | 
 | 		 */ | 
 | 		w->utflastboff = boff; | 
 | 		w->utflastq = q; | 
 | 		nr = q1-q; | 
 | 		if(nr > BUFSIZE/UTFmax) | 
 | 			nr = BUFSIZE/UTFmax; | 
 | 		bufread(&t->file->b, q, r, nr); | 
 | 		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r); | 
 | 		if(boff >= off){ | 
 | 			m = nb; | 
 | 			if(boff+m > off+x->fcall.count) | 
 | 				m = off+x->fcall.count - boff; | 
 | 			memmove(b1+n, b, m); | 
 | 			n += m; | 
 | 		}else if(boff+nb > off){ | 
 | 			if(n != 0) | 
 | 				error("bad count in utfrune"); | 
 | 			m = nb - (off-boff); | 
 | 			if(m > x->fcall.count) | 
 | 				m = x->fcall.count; | 
 | 			memmove(b1, b+(off-boff), m); | 
 | 			n += m; | 
 | 		} | 
 | 		boff += nb; | 
 | 		q += nr; | 
 | 	} | 
 | 	fbuffree(r); | 
 | 	fbuffree(b); | 
 | 	fc.count = n; | 
 | 	fc.data = b1; | 
 | 	respond(x, &fc, nil); | 
 | 	fbuffree(b1); | 
 | } | 
 |  | 
 | int | 
 | xfidruneread(Xfid *x, Text *t, uint q0, uint q1) | 
 | { | 
 | 	Fcall fc; | 
 | 	Window *w; | 
 | 	Rune *r, junk; | 
 | 	char *b, *b1; | 
 | 	uint q, boff; | 
 | 	int i, rw, m, n, nr, nb; | 
 |  | 
 | 	w = t->w; | 
 | 	wincommit(w, t); | 
 | 	r = fbufalloc(); | 
 | 	b = fbufalloc(); | 
 | 	b1 = fbufalloc(); | 
 | 	n = 0; | 
 | 	q = q0; | 
 | 	boff = 0; | 
 | 	while(q<q1 && n<x->fcall.count){ | 
 | 		nr = q1-q; | 
 | 		if(nr > BUFSIZE/UTFmax) | 
 | 			nr = BUFSIZE/UTFmax; | 
 | 		bufread(&t->file->b, q, r, nr); | 
 | 		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r); | 
 | 		m = nb; | 
 | 		if(boff+m > x->fcall.count){ | 
 | 			i = x->fcall.count - boff; | 
 | 			/* copy whole runes only */ | 
 | 			m = 0; | 
 | 			nr = 0; | 
 | 			while(m < i){ | 
 | 				rw = chartorune(&junk, b+m); | 
 | 				if(m+rw > i) | 
 | 					break; | 
 | 				m += rw; | 
 | 				nr++; | 
 | 			} | 
 | 			if(m == 0) | 
 | 				break; | 
 | 		} | 
 | 		memmove(b1+n, b, m); | 
 | 		n += m; | 
 | 		boff += nb; | 
 | 		q += nr; | 
 | 	} | 
 | 	fbuffree(r); | 
 | 	fbuffree(b); | 
 | 	fc.count = n; | 
 | 	fc.data = b1; | 
 | 	respond(x, &fc, nil); | 
 | 	fbuffree(b1); | 
 | 	return q-q0; | 
 | } | 
 |  | 
 | void | 
 | xfideventread(Xfid *x, Window *w) | 
 | { | 
 | 	Fcall fc; | 
 | 	int i, n; | 
 |  | 
 | 	i = 0; | 
 | 	x->flushed = FALSE; | 
 | 	while(w->nevents == 0){ | 
 | 		if(i){ | 
 | 			if(!x->flushed) | 
 | 				respond(x, &fc, "window shut down"); | 
 | 			return; | 
 | 		} | 
 | 		w->eventx = x; | 
 | 		winunlock(w); | 
 | 		recvp(x->c); | 
 | 		winlock(w, 'F'); | 
 | 		i++; | 
 | 	} | 
 |  | 
 | 	n = w->nevents; | 
 | 	if(n > x->fcall.count) | 
 | 		n = x->fcall.count; | 
 | 	fc.count = n; | 
 | 	fc.data = w->events; | 
 | 	respond(x, &fc, nil); | 
 | 	w->nevents -= n; | 
 | 	if(w->nevents){ | 
 | 		memmove(w->events, w->events+n, w->nevents); | 
 | 		w->events = erealloc(w->events, w->nevents); | 
 | 	}else{ | 
 | 		free(w->events); | 
 | 		w->events = nil; | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | xfidindexread(Xfid *x) | 
 | { | 
 | 	Fcall fc; | 
 | 	int i, j, m, n, nmax, isbuf, cnt, off; | 
 | 	Window *w; | 
 | 	char *b; | 
 | 	Rune *r; | 
 | 	Column *c; | 
 |  | 
 | 	qlock(&row.lk); | 
 | 	nmax = 0; | 
 | 	for(j=0; j<row.ncol; j++){ | 
 | 		c = row.col[j]; | 
 | 		for(i=0; i<c->nw; i++){ | 
 | 			w = c->w[i]; | 
 | 			nmax += Ctlsize + w->tag.file->b.nc*UTFmax + 1; | 
 | 		} | 
 | 	} | 
 | 	nmax++; | 
 | 	isbuf = (nmax<=RBUFSIZE); | 
 | 	if(isbuf) | 
 | 		b = (char*)x->buf; | 
 | 	else | 
 | 		b = emalloc(nmax); | 
 | 	r = fbufalloc(); | 
 | 	n = 0; | 
 | 	for(j=0; j<row.ncol; j++){ | 
 | 		c = row.col[j]; | 
 | 		for(i=0; i<c->nw; i++){ | 
 | 			w = c->w[i]; | 
 | 			/* only show the currently active window of a set */ | 
 | 			if(w->body.file->curtext != &w->body) | 
 | 				continue; | 
 | 			winctlprint(w, b+n, 0); | 
 | 			n += Ctlsize; | 
 | 			m = min(RBUFSIZE, w->tag.file->b.nc); | 
 | 			bufread(&w->tag.file->b, 0, r, m); | 
 | 			m = n + snprint(b+n, nmax-n-1, "%.*S", m, r); | 
 | 			while(n<m && b[n]!='\n') | 
 | 				n++; | 
 | 			b[n++] = '\n'; | 
 | 		} | 
 | 	} | 
 | 	qunlock(&row.lk); | 
 | 	off = x->fcall.offset; | 
 | 	cnt = x->fcall.count; | 
 | 	if(off > n) | 
 | 		off = n; | 
 | 	if(off+cnt > n) | 
 | 		cnt = n-off; | 
 | 	fc.count = cnt; | 
 | 	memmove(r, b+off, cnt); | 
 | 	fc.data = (char*)r; | 
 | 	if(!isbuf) | 
 | 		free(b); | 
 | 	respond(x, &fc, nil); | 
 | 	fbuffree(r); | 
 | } |