shithub: purgatorio

ref: 3efb5bbb4061056e523858b134c555949591efe2
dir: /appl/wm/samstub.b/

View raw version
implement Samstub;

include "sys.m";
sys: Sys;
fprint, FD, fildes: import sys;

stderr: ref FD;

include "draw.m";
draw: Draw;

include "samterm.m";
samterm: Samterm;
Text, Menu, Context, Flayer, Section: import samterm;

include "samtk.m";
samtk: Samtk;
panic, whichtext, whichmenu: import samtk;

include "samstub.m";

sendsam:	chan of ref Sammsg;
recvsam:	chan of ref Sammsg;

snarflen:	int;

ctxt: ref Context;

requested: list of (int, int);

tname := array [] of {
	"Tversion",
	"Tstartcmdfile",
	"Tcheck",
	"Trequest",
	"Torigin",
	"Tstartfile",
	"Tworkfile",
	"Ttype",
	"Tcut",
	"Tpaste",
	"Tsnarf",
	"Tstartnewfile",
	"Twrite",
	"Tclose",
	"Tlook",
	"Tsearch",
	"Tsend",
	"Tdclick",
	"Tstartsnarf",
	"Tsetsnarf",
	"Tack",
	"Texit",
};

hname := array [] of {
	"Hversion",
	"Hbindname",
	"Hcurrent",
	"Hnewname",
	"Hmovname",
	"Hgrow",
	"Hcheck0",
	"Hcheck",
	"Hunlock",
	"Hdata",
	"Horigin",
	"Hunlockfile",
	"Hsetdot",
	"Hgrowdata",
	"Hmoveto",
	"Hclean",
	"Hdirty",
	"Hcut",
	"Hsetpat",
	"Hdelname",
	"Hclose",
	"Hsetsnarf",
	"Hsnarflen",
	"Hack",
	"Hexit",
};

init(c: ref Context)
{
	ctxt = c;
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;

	stderr = fildes(2);

	samterm = load Samterm Samterm->PATH;

	samtk = load Samtk Samtk->PATH;
	samtk->init(ctxt);

	requested = nil;
}

start(): (ref Samio, chan of ref Sammsg)
{
	sys = load Sys Sys->PATH;

	sys->bind("#C", "/", sys->MAFTER);

	# Allocate a cmd device
	ctl := sys->open("/cmd/clone", sys->ORDWR);
	if(ctl == nil) {
		fprint(stderr, "can't open /cmd/clone\n");
		return (nil, nil);
	}

	# Find out which one
	buf := array[32] of byte;
	n := sys->read(ctl, buf, len buf);
	if(n <= 0) {
		fprint(stderr, "can't read cmd device\n");
		return (nil, nil);
	}

	dir := "/cmd/"+string buf[0:n];

	# Start the Command
	n = sys->fprint(ctl, "exec "+ SAM);
	if(n <= 0) {
		fprint(stderr, "can't exec %s\n", SAM);
		return (nil, nil);
	}

	data := sys->open(dir+"/data", sys->ORDWR);
	if(data == nil) {
		fprint(stderr, "can't open cmd data file\n");
		return (nil, nil);
	}

	sendsam = chan of ref Sammsg;
	recvsam = chan of ref Sammsg;

	samio := ref Samio(ctl, data, array[1] of byte, 0, 0);

	spawn sender(samio, sendsam);
	spawn receiver(samio, recvsam);

	return (samio, recvsam);
}

sender(samio: ref Samio, c: chan of ref Sammsg)
{
	fprint(ctxt.logfd, "sender started\n");
	for (;;) {
		h := <- c;
		if (h == nil) return;
		buf := array[3 + len h.mdata] of byte;
		buf[0] = byte h.mtype;
		buf[1] = byte h.mcount;
		buf[2] = byte (h.mcount >> 8);
		buf[3:] = h.mdata;
		sys->write(samio.data, buf, len buf);
	}
}

receiver(samio: ref Samio, msgchan: chan of ref Sammsg)
{
	c: int;

	fprint(ctxt.logfd, "receiver started\n");

	state := 0;
	i := 0;
	errs := 0;

	h: ref Sammsg;

	for (;;) {
		if (samio.count == 0) {
			n := sys->read(samio.data, samio.buffer, len samio.buffer);
			if (n <= 0) {
				fprint(stderr, "Read error on sam's pipe\n");
				return;
			}
			samio.index = 0;
			samio.count = n;
		}
		samio.count--;

		c = int samio.buffer[samio.index++];

		case state {
		0 =>
			h = ref Sammsg(c, 0, nil);
			state++;
			continue;
		1 =>
			h.mcount = c;
			state++;
			continue;
		2 =>
			h.mcount = h.mcount|(c<<8);
			if (h.mcount > DATASIZE || h.mcount < 0)
				panic("receiver: count>DATASIZE");
			if(h.mcount != 0) {
				h.mdata = array[h.mcount] of byte;
				i = 0;
				state++;
				continue;
			}
		3 =>
			h.mdata[i++] = byte c;
			if(i < h.mcount){
				continue;
			}
		}
		msgchan <- = h;
		h = nil;
		state = 0;
	}
}

inmesg(h: ref Sammsg): int
{

	case h.mtype {

	Hversion =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hversion: %d\n", m);

	Hbindname =>
		m := h.inshort(0);
		vl := h.invlong(2);
		fprint(ctxt.logfd, "Hbindname: %ux, %bux\n", m, vl);
		bindname(m, int vl);

	Hcurrent =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hcurrent: %d\n", m);
		hcurrent(m);

	Hmovname =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hmovname: %d, %s\n", m, string h.mdata[2:]);
		movename(m, string h.mdata[2:]);

	Hgrow =>
		m := h.inshort(0);
		l1 := h.inlong(2);
		l2 := h.inlong(6);
		fprint(ctxt.logfd, "Hgrow: %d, %d, %d\n", m, l1, l2);
		hgrow(m, l1, l2);

	Hnewname =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hnewname: %d\n", m);
		newname(m);

	Hcheck0 =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hcheck0: %d\n", m);
		i := whichmenu(m);
		if (i >= 0) {
			t := ctxt.menus[i].text;
			if (t != nil)
				t.lock++;
			outTs(Tcheck, m);
		}

	Hcheck =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hcheck: %d\n", m);
		i := whichmenu(m);
		if (i >= 0) {
			t := ctxt.menus[i].text;
			if (t != nil && t.lock)
				t.lock--;
			hcheck(t);
		}

	Hunlock =>
		fprint(ctxt.logfd, "Hunlock\n");
		clrlock();

	Hdata =>
		m := h.inshort(0);
		l := h.inlong(2);
		fprint(ctxt.logfd, "Hdata: %d, %d, %s\n",
			m, l, contract(string h.mdata[6:]));
		hdata(m, l, string h.mdata[6:]);

	Horigin =>
		m := h.inshort(0);
		l := h.inlong(2);
		fprint(ctxt.logfd, "Horigin: %d, %d\n", m, l);
		horigin(m, l);

	Hunlockfile =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hunlockfile: %d\n", m);
		clrlock();

	Hsetdot =>
		m := h.inshort(0);
		l1 := h.inlong(2);
		l2 := h.inlong(6);
		fprint(ctxt.logfd, "Hsetdot: %d, %d, %d\n", m, l1, l2);
		hsetdot(m, l1, l2);

	Hgrowdata =>
		m := h.inshort(0);
		l1 := h.inlong(2);
		l2 := h.inlong(6);
		fprint(ctxt.logfd, "Hgrowdata: %d, %d, %d, %s\n",
			m, l1, l2, contract(string h.mdata[10:]));
		hgrowdata(m, l1, l2, string h.mdata[10:]);

	Hmoveto =>
		m := h.inshort(0);
		l := h.inlong(2);
		fprint(ctxt.logfd, "Hmoveto: %d, %d\n", m, l);
		hmoveto(m, l);

	Hclean =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hclean: %d\n", m);
		hclean(m);

	Hdirty =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hdirty: %d\n", m);
		hdirty(m);

	Hdelname =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hdelname: %d\n", m);
		hdelname(m);

	Hcut =>
		m := h.inshort(0);
		l1 := h.inlong(2);
		l2 := h.inlong(6);
		fprint(ctxt.logfd, "Hcut: %d, %d, %d\n",
			m, l1, l2);
		hcut(m, l1, l2);

	Hclose =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hclose: %d\n", m);
		hclose(m);

	Hsetpat =>
		fprint(ctxt.logfd, "Hsetpat: %s\n", string h.mdata);
		samtk->hsetpat(string h.mdata);

	Hsetsnarf =>
		m := h.inshort(0);
		fprint(ctxt.logfd, "Hsetsnarf: %d\n", m);

	Hsnarflen =>
		snarflen = h.inlong(0);
		fprint(ctxt.logfd, "Hsnarflen: %d\n", snarflen);

	Hack =>
		fprint(ctxt.logfd, "Hack\n");
		outT0(Tack);

	Hexit =>
		fprint(ctxt.logfd, "Hexit\n");
		return 1;

	-1 =>
		panic("rcv error");

	* =>
		fprint(ctxt.logfd, "type %d\n", h.mtype);
		panic("rcv unknown");
	}
	return 0;
}

Sammsg.inshort(h: self ref Sammsg, n: int): int
{
	return	((int h.mdata[n+1])<<8) |
		((int h.mdata[n]));
}

Sammsg.inlong(h: self ref Sammsg, n: int): int
{
	return	((int h.mdata[n+3])<<24) |
		((int h.mdata[n+2])<<16) |
		((int h.mdata[n+1])<< 8) |
		((int h.mdata[n]));
}

Sammsg.invlong(h: self ref Sammsg, n: int): big
{
	return	((big h.mdata[n+7])<<56) |
		((big h.mdata[n+6])<<48) |
		((big h.mdata[n+5])<<40) |
		((big h.mdata[n+4])<<32) |
		((big h.mdata[n+3])<<24) |
		((big h.mdata[n+2])<<16) |
		((big h.mdata[n+1])<< 8) |
		((big h.mdata[n]));
}

Sammsg.outcopy(h: self ref Sammsg, pos: int, data: array of byte)
{
	h.mdata[pos:] = data;
}

Sammsg.outshort(h: self ref Sammsg, pos: int, s: int)
{
	h.mdata[pos++]	= byte s;
	h.mdata[pos]	= byte (s >> 8);
}

Sammsg.outlong(h: self ref Sammsg, pos: int, s: int)
{
	h.mdata[pos++]	= byte s;
	h.mdata[pos++]	= byte (s >> 8);
	h.mdata[pos++]	= byte (s >> 16);
	h.mdata[pos]	= byte (s >> 24);
}

Sammsg.outvlong(h: self ref Sammsg, pos: int, s: big)
{
	h.mdata[pos++]	= byte s;
	h.mdata[pos++]	= byte (s >> 8);
	h.mdata[pos++]	= byte (s >> 16);
	h.mdata[pos++]	= byte (s >> 24);
	h.mdata[pos++]	= byte (s >> 32);
	h.mdata[pos++]	= byte (s >> 40);
	h.mdata[pos++]	= byte (s >> 48);
	h.mdata[pos]	= byte (s >> 56);
}

outT0(t: int)
{
	fprint(ctxt.logfd, "\t\t\t\t\t%s\n", tname[t]);
	h := ref Sammsg(t, 0, nil);
	sendsam <- = h;
}

outTs(t, s: int)
{
	fprint(ctxt.logfd, "\t\t\t\t\t%s %ux\n", tname[t], s);
	a := array[2] of byte;
	h := ref Sammsg(t, 2, a);
	h.outshort(0, s);
	sendsam <- = h;
}
	
outTv(t: int, i: big)
{
	fprint(ctxt.logfd, "\t\t\t\t\t%s %bux\n", tname[t], i);
	a := array[8] of byte;
	h := ref Sammsg(t, 8, a);
	h.outvlong(0, i);
	sendsam <- = h;
}

outTsll(t, m, l1, l2: int)
{	fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2);
	a := array[10] of byte;
	h := ref Sammsg(t, 10, a);
	h.outshort(0, m);
	h.outlong(2, l1);
	h.outlong(6, l2);
	sendsam <- = h;
}

outTsl(t, m, l: int)
{	fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d\n", tname[t], m, l);
	a := array[6] of byte;
	h := ref Sammsg(t, 6, a);
	h.outshort(0, m);
	h.outlong(2, l);
	sendsam <- = h;
}

outTsls(t, m, l1, l2: int)
{	fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2);
	a := array[8] of byte;
	h := ref Sammsg(t, 8, a);
	h.outshort(0, m);
	h.outlong(2, l1);
	h.outshort(6, l2);
	sendsam <- = h;
}
	
outTslS(t, s1, l1: int, s: string)
{
	fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %s\n", tname[t], s1, l1, s);
	a := array[6 + len array of byte s] of byte;
	h := ref Sammsg(t, len a, a);
	h.outshort(0, s1);
	h.outlong(2, l1);
	h.outcopy(6, array of byte s);
	sendsam <- = h;
}

newname(tag: int)
{
	menuins(0, "dummy", nil, tag);
}

bindname(tag, l: int)
{
	if ((m := whichmenu(tag)) < 0) panic("bindname: whichmenu");
	if ((l = whichtext(l)) < 0) panic("bindname: whichtext");
	if (ctxt.menus[m].text != nil)
		return;		# Already bound
	t := ctxt.texts[l];
	t.tag = tag;
	for (fls := t.flayers; fls != nil; fls = tl fls) (hd fls).tag = tag;
	ctxt.menus[m].text = t;
}

menuins(m: int, s: string, t: ref Text, tag: int)
{
	newmenus := array [len ctxt.menus+1] of ref Menu;
	menu := ref Menu(
		tag,	# tag
		s,	# name
		t	# text
	);
	if (m > 0)
		newmenus[0:] = ctxt.menus[0:m];
	newmenus[m] = menu;
	if (m < len ctxt.menus)
		newmenus[m+1:] = ctxt.menus[m:];	
	ctxt.menus = newmenus;

	samtk->menuins(m, s);
}

menudel(m: int)
{
	if (len ctxt.menus == 0 || m >= len ctxt.menus || ctxt.menus[m].text != nil)
		panic("menudel");
	newmenus := array [len ctxt.menus - 1] of ref Menu;
	newmenus[0:] = ctxt.menus[0:m];
	newmenus[m:] = ctxt.menus[m+1:];
	ctxt.menus = newmenus;
	samtk->menudel(m);
}

outcmd() {
	if(ctxt.work != nil) {
		fl := ctxt.work;
		outTsll(Tworkfile, fl.tag, fl.dot.first, fl.dot.last);
	}
}

hclose(m: int)
{
	i: int;

	# close LAST window of a file
	if((m = whichmenu(m)) < 0) panic("hclose: whichmenu");
	t := ctxt.menus[m].text;
	if (tl t.flayers != nil) panic("hclose: flayers");
	fl := hd t.flayers;
	fl.t = nil;
	for (i = 0; i< len ctxt.flayers; i++)
		if (ctxt.flayers[i] == fl) break;
	if (i == len ctxt.flayers) panic("hclose: ctxt.flayers");
	samtk->chandel(i);
	t.flayers = nil;
	for (i = 0; i< len ctxt.texts; i++)
		if (ctxt.texts[i] == ctxt.menus[m].text) break;
	if (i == len ctxt.texts) panic("hclose: ctxt.texts");
	ctxt.texts[i:] = ctxt.texts[i+1:];
	ctxt.texts = ctxt.texts[:len ctxt.texts - 1];
	ctxt.menus[m].text = nil;
	ctxt.which = nil;
	samtk->focus(hd ctxt.cmd.flayers);
}

close(win, tag: int)
{
	nfls: list of ref Flayer;

	if ((m := whichtext(tag)) < 0) panic("close: text");
	t := ctxt.texts[m];
	if ((m = whichmenu(tag)) < 0) panic("close: menu");
	if (len t.flayers == 1) {
		outTs(Tclose, tag);
		setlock();
		return;
	}
	fl := ctxt.flayers[win];
	nfls = nil;
	for (fls := t.flayers; fls != nil; fls = tl fls)
		if (hd fls != fl) nfls = hd fls :: nfls;
	t.flayers = nfls;
	samtk->chandel(win);
	fl.t = nil;
	samtk->settitle(t, ctxt.menus[m].name);
	ctxt.which = nil;
}

hdelname(m: int)
{
	# close LAST window of a file
	if((m = whichmenu(m)) < 0) panic("hdelname: whichmenu");
	if (ctxt.menus[m].text != nil) panic("hdelname: text");
	ctxt.menus[m:] = ctxt.menus[m+1:];
	ctxt.menus = ctxt.menus[:len ctxt.menus - 1];
	samtk->menudel(m);
	ctxt.which = nil;
}

hdirty(m: int)
{
	if((m = whichmenu(m)) < 0) panic("hdirty: whichmenu");
	if (ctxt.menus[m].text == nil) panic("hdirty: text");
	ctxt.menus[m].text.state |= Samterm->Dirty;
	samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name);
}

hclean(m: int)
{
	if((m = whichmenu(m)) < 0) panic("hclean: whichmenu");
	if (ctxt.menus[m].text == nil) panic("hclean: text");
	ctxt.menus[m].text.state &= ~Samterm->Dirty;
	samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name);
}

movename(tag: int, s: string)
{
	i := whichmenu(tag);
	if (i < 0) panic("movename: whichmenu");

	t := ctxt.menus[i].text;

	ctxt.menus[i].text = nil;	# suppress panic in menudel
	menudel(i);

	if(t == ctxt.cmd)
		i = 0;
	else {
		if (len ctxt.menus > 0 && ctxt.menus[0].text == ctxt.cmd)
			i = 1;
		else
			i = 0;
		for(; i < len ctxt.menus; i++) {
			if (s < ctxt.menus[i].name)
				break;
		}
	}
	if (t != nil) samtk->settitle(t, s);
	menuins(i, s, t, tag);
}

hcheck(t: ref Text)
{
	if (t == nil) {
		fprint(ctxt.logfd, "hcheck: no text in menu entry\n");
		return;
	}
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		scrollto(fl, fl.scope.first);
	}
}

setlock()
{
	ctxt.lock++;
	samtk->allflayers("cursor -bitmap cursor.wait");
}

clrlock()
{
	if (ctxt.lock > 0)
		ctxt.lock--;
	else
		fprint(ctxt.logfd, "lock: wasn't locked\n");
	if (ctxt.lock == 0)
		samtk->allflayers("cursor -default; update");
}

hcut(m, where, howmuch: int)
{
	if((m = whichmenu(m)) < 0) panic("hcut: whichmenu");
	t := ctxt.menus[m].text;
	if (t == nil) panic("hcut -- no text");

#	sctdump(t.sects, "Hcut, before");
	t.nrunes -= howmuch;
	t.sects = sctdelete(t.sects, where, howmuch);
#	sctdump(t.sects, "Hcut, after");
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		if (where < fl.scope.first) {
			if (where + howmuch <= fl.scope.first)
				fl.scope.first -= howmuch;
			else
				fl.scope.first = where;
		}
		if (where < fl.scope.last) {
			if (where + howmuch <= fl.scope.last)
				fl.scope.last -= howmuch;
			else
				fl.scope.last = where;
		}
	}
}

hgrow(tag, l1, l2: int)
{
	if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu");
	t := ctxt.menus[m].text;
	grow(t, l1, l2);
}

hdata(m, l: int, s: string)
{
	nr: list of (int, int);

	if((m = whichmenu(m)) < 0) panic("hdata: whichmenu");
	t := ctxt.menus[m].text;
	if (t == nil) panic("hdata -- no text");
	if (s != "") {
		t.sects = sctput(t.sects, l, s);
		updatefls(t, l, s);
	}
	for (nr = nil; requested != nil; requested = tl requested) {
		(r1, r2) := hd requested;
		if (r1 != m || r2 != l)
			nr = (r1, r2) :: nr;
	}
	requested = nr;
	clrlock();
}

hgrowdata(tag, l1, l2: int, s: string)
{
	if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu");
	t := ctxt.menus[m].text;
	if (t == nil) panic("hdata -- no text");
	grow(t, l1, l2);
	t.sects = sctput(t.sects, l1, s);
	updatefls(t, l1, s);
}

hsetdot(m, l1, l2: int)
{
	if((m = whichmenu(m)) < 0) panic("hsetdot: whichmenu");
	t := ctxt.menus[m].text;
	if (t == nil || t.flayers == nil) panic("hsetdot -- no text");
	samtk->setdot(hd t.flayers, l1, l2);
}

hcurrent(tag: int)
{
	if ((i := whichmenu(tag)) < 0) panic("hcurrent: whichmenu");
	if (ctxt.menus[i].text == nil) {
		n := startfile(tag);
		ctxt.menus[i].text = ctxt.texts[n];
		if (ctxt.menus[i].name != nil)
			samtk->settitle(ctxt.texts[n], ctxt.menus[i].name);
	}
	ctxt.work = hd ctxt.menus[i].text.flayers;
}

hmoveto(m, l: int)
{
	if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu");
	t := ctxt.menus[m].text;
	fl := hd t.flayers;
	if (fl.scope.first <= l &&
	   (l < fl.scope.last || fl.scope.last == fl.scope.first))
		return;
	(n, p) := sctrevcnt(t.sects, l, fl.lines/2);
#	fprint(ctxt.logfd, "hmoveto: (n, p) = (%d, %d)\n", n, p);
	if (n < 0) {
		outTsll(Torigin, t.tag, l, fl.lines/2);
		setlock();
		return;
	}
	scrollto(fl, p);
}

startcmdfile()
{
	t := ctxt.tag++;
	n := newtext(t, 1);
	ctxt.cmd = ctxt.texts[n];
	outTv(Tstartcmdfile, big t);
}

startnewfile()
{
	t := ctxt.tag++;
	n := newtext(t, 0);
	outTv(Tstartnewfile, big t);
}

startfile(tag: int): int
{
	n := newtext(tag, 0);
	outTv(Tstartfile, big tag);
	setlock();
	return n;
}

horigin(m, l: int)
{
	if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu");
	t := ctxt.menus[m].text;
	fl := hd t.flayers;
	scrollto(fl, l);
	clrlock();
}

scrollto(fl: ref Flayer, where: int)
{
	s: string;
	n: int;

	tag := fl.tag;
	if ((i := whichtext(tag)) < 0) panic("scrollto: whichtext");
	t := ctxt.texts[i];
	
	samtk->flclear(fl);
	(n, s) = sctgetlines(t.sects, where, fl.lines);
	fl.scope.first = where;
	fl.scope.last = where + len s;
	if (s != "")
		samtk->flinsert(fl, where, s);
	if (n == 0) {
		samtk->setscrollbar(t, fl);
	} else {
		(h, l) := scthole(t, fl.scope.last);
		fl.scope.last = h;
		if (l > 0)
			outrequest(tag, h, l);
		else
			if (fl.scope.first > t.nrunes) {
				fl.scope.first = t.nrunes;
				fl.scope.last = t.nrunes;
				samtk->setscrollbar(t, fl);
			}
	}
}

scthole(t: ref Text, f: int): (int, int)
{
	p := 0;
	h := -1;
	l := 0;
	for (scts := t.sects; scts != nil; scts = tl scts) {
		sct := hd scts;
		nr := sct.nrunes;
		nt := len sct.text;
		if (h >= 0) {
			if (sct.text == "") {
				l += nr;
				if (l >= 512) return (h,512);
			} else
				return (h,l);
		}
		if (h < 0 && f < nr) {
			if (nt < nr) {
				if (f < nt) {
					h = p + nt;
					l = nr - nt;
				} else {
					h = p + f;
					l = nr - f;
				}
				if (l >= 512) return (h,512);
			}
		}
		p += sct.nrunes;
		f -= sct.nrunes;
	}
	if (h == -1) return (p, 0);
	return (h, l);
}

# return (x, p): x = -1: p -> hole; x = 0: p -> line n; x > 0: p -> eof
sctlinecount(t: ref Text, pos, n: int): (int, int)
{
	i: int;

	p := 0;
	for (scts := t.sects; scts != nil; scts = tl scts) {
		sct := hd scts;
		nr := sct.nrunes;
		nt := len sct.text;
		if (pos < nr) {
			if (pos > 0) i = pos; else i = 0;
			while (i < nt) {
				if (sct.text[i++] == '\n') n--;
				if (n == 0) return (0, p + i);
			}
			if (nt < nr) return (-1, p + nt);
		}
		p += sct.nrunes;
		pos -= sct.nrunes;
	}
	return (n, p);
}

sctrevcnt(scts: list of ref Section, pos, n: int): (int, int)
{
	if (scts == nil) return (n, 0);
	sct := hd scts;
	scts = tl scts;
	nt := len sct.text;
	nr := sct.nrunes;
	if (pos >= nr) {
		(n, pos) = sctrevcnt(scts, pos - nr, n);
		pos += nr;
	}
	if (n > 0) {
		if (nt < nr && pos > nt)
			return(-1, pos);
		for (i := pos-1; i >= 0; i--) {
			if (sct.text[i] == '\n') n--;
			if (n == 0) break;
		}
		return (n, i + 1);	
	}
	return (n, pos);
}

insertfls(t: ref Text, l: int, s: string)
{
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		if (l < fl.scope.first || l > fl.scope.last) continue;
		samtk->flinsert(fl, l, s);
		samtk->setscrollbar(t, fl);
		fl.scope.last += len s;
	}
}

updatefls(t: ref Text, l: int, s: string)
{
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		if (l < fl.scope.first || l > fl.scope.last) continue;
		samtk->flinsert(fl, l, s);
		(x, p) := sctlinecount(t, fl.scope.first, fl.lines);
		fl.scope.last = p;
		if (x >= 0) {
			if (p > l + len s) {
				samtk->flinsert(fl, l + len s,
					sctget(t.sects, l + len s, p));
			}
			if (x == 0)
				samtk->fldelexcess(fl);
		} else {
			(h1, h2) := scthole(t, l);
			fl.scope.last = h1;
			if (h2 > 0) {
				outrequest(t.tag, h1, h2);
				continue;
			} else {
				panic("Can't happen ??");
			}
		}
		samtk->setscrollbar(t, fl);
	}
}

outrequest(tag, h1, h2: int) {
	for (l := requested; l != nil; l = tl l) {
		(r1, r2) := hd l;
		if (r1 == tag && r2 == h1) return;
	}
	outTsls(Trequest, tag, h1, h2);
	requested = (tag, h1) :: requested;
	setlock();
}

deletefls(t: ref Text, pos, nbytes: int)
{
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		if (pos >= fl.scope.last) continue;
		if (pos + nbytes <= fl.scope.first || pos >= fl.scope.last) {
			fl.scope.first -= nbytes;
			fl.scope.last -= nbytes;
			continue;
		}
		samtk->fldelete(fl, pos, pos + nbytes);
		(x, p) := sctlinecount(t, fl.scope.first, fl.lines);
		if (x >= 0 && p > fl.scope.last) {
			samtk->flinsert(fl, fl.scope.last,
				sctget(t.sects, fl.scope.last, p));
			fl.scope.last = p;
		} else {
			fl.scope.last = p;
			(h1, h2) := scthole(t, fl.scope.last);
			if (h2 > 0)
				outrequest(t.tag, h1, h2);
		}
		samtk->setscrollbar(t, fl);
	}
}

contract(s: string): string
{
	if (len s < 32)
		cs := s;
	else
		cs = s[0:16] + " ... " + s[len s - 16:];
	for (i := 0; i < len cs; i++)
		if (cs[i] == '\n') cs[i] = '\u008a';
	return cs;
}

cleanout()
{
	if ((fl := ctxt.which) == nil) return;
	if ((i := whichtext(fl.tag)) < 0) panic("cleanout: whichtext");
	t := ctxt.texts[i];

	if (fl.typepoint >= 0 && fl.dot.first > fl.typepoint) {
		s := sctget(t.sects, fl.typepoint, fl.dot.first);
		outTslS(Samstub->Ttype, fl.tag, fl.typepoint, s);
		t.state &= ~Samterm->LDirty;
	}
	fl.typepoint = -1;
}

newtext(tag, tp: int): int
{
	n := len ctxt.texts;
	t := ref Text(
		tag,					# tag
		0,					# lock
		samtk->newflayer(tag, tp) :: nil,	# flayers
		0,					# nrunes
		nil,					# sects
		0					# state
	);
	texts := array [n + 1] of ref Text;
	texts[0:] = ctxt.texts;
	texts[n] = t;
	ctxt.texts = texts;
	samtk->newcur(t, hd t.flayers);
	return n;
}

keypress(key: string)
{
	# Find text and flayer
	fl := ctxt.which;
	tag := fl.tag;
	if ((i := whichtext(tag)) < 0) panic("keypress: whichtext");
	t := ctxt.texts[i];

	if (fl.dot.last != fl.dot.first) {
		cut(t, fl);
	}

	case (key) {
	"\b" =>
		if (t.nrunes == 0 || fl.dot.first == 0)
			return;
		fl.dot.first--;
		if (fl.typepoint >= 0 && fl.dot.first >= fl.typepoint) {
			t.nrunes -= fl.dot.last - fl.dot.first;
			t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first);
			deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first);
			if (fl.dot.first == fl.typepoint) {
				fl.typepoint = -1;
				t.state &= ~Samterm->LDirty;
				if ((i = whichmenu(tag)) < 0)
					panic("keypress: whichmenu");
				samtk->settitle(t, ctxt.menus[i].name);
			}
		} else {
			cut(t, fl);
		}
	* =>
		if (fl.typepoint < 0) {
			fl.typepoint = fl.dot.first;
			t.state |= Samterm->LDirty;
			if ((i = whichmenu(tag)) < 0)
				panic("keypress: whichmenu");
			samtk->settitle(t, ctxt.menus[i].name);
		}
		if (fl.dot.first > t.nrunes)
			panic("keypress -- cursor > file len");
		t.sects = sctmakeroom(t.sects, fl.dot.first, len key);
		t.nrunes += len key;
		t.sects = sctput(t.sects, fl.dot.first, key);
		insertfls(t, fl.dot.first, key);
		f := fl.dot.first + len key;
		samtk->setdot(fl, f, f);
		if (key == "\n") {
			if (f >= fl.scope.last) {
				(n, p) := sctrevcnt(t.sects, f-1, 2*fl.lines/3);
				if (n < 0) {
					outTsll(Torigin, t.tag, f-1, 2*fl.lines/3);
					setlock();
				} else {
					scrollto(fl, p);
				}
			}
			if (t == ctxt.cmd && fl.dot.last == t.nrunes) {
				outcmd();
				setlock();
			}
			cleanout();
		}
	}
	return;
}

cut(t: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("cut: typepoint");
	outTsll(Tcut, fl.tag, fl.dot.first, fl.dot.last);
	t.nrunes -= fl.dot.last - fl.dot.first;
	t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first);
	deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first);
}

paste(t: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("paste: typepoint");
	if (snarflen == 0) return;
	if (fl.dot.first < fl.dot.last) cut(t, fl);
	outTsl(Tpaste, fl.tag, fl.dot.first);
}

snarf(nil: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("snarf: typepoint");
	if (fl.dot.first == fl.dot.last) return;
	snarflen = fl.dot.last - fl.dot.first;
	outTsll(Tsnarf, fl.tag, fl.dot.first, fl.dot.last);
}

look(nil: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("look: typepoint");
	outTsll(Tlook, fl.tag, fl.dot.first, fl.dot.last);
	setlock();
}

send(nil: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("send: typepoint");
	outcmd();
	outTsll(Tsend, fl.tag, fl.dot.first, fl.dot.last);
	setlock();
}

search(nil: ref Text, fl: ref Flayer)
{
	if (fl.typepoint >= 0) panic("search: typepoint");
	outcmd();
	outT0(Tsearch);
	setlock();
}

zerox(t: ref Text)
{
	fl := samtk->newflayer(t.tag, ctxt.cmd == t);
	t.flayers = fl :: t.flayers;
	m := whichmenu(t.tag);
	samtk->settitle(t, ctxt.menus[m].name);
	samtk->newcur(t, fl);
	scrollto(fl, 0);
}

sctget(scts: list of ref Section, p1, p2: int): string
{
	while (scts != nil) {
		sct := hd scts; scts = tl scts;
		ln := len sct.text;
		if (p1 < sct.nrunes) {
			if (ln < sct.nrunes && p2 > ln) {
				sctdump(scts, "panic");
				panic("sctget - asking for a hole");
			}
			if (p2 > sct.nrunes) {
				s := sct.text[p1:];
				return s + sctget(scts, 0, p2 - ln);
			}
			return sct.text[p1:p2];
		}
		p1 -= sct.nrunes;
		p2 -= sct.nrunes;
	}
	return "";
}

sctgetlines(scts: list of ref Section, p, n: int): (int, string)
{
	s := "";
	while (scts != nil) {
		sct := hd scts; scts = tl scts;
		ln := len sct.text;
		if (p < sct.nrunes) {
			if (p > ln) return (n, s);
			if (p > 0) b := p; else b = 0;
			for (i := b; i < ln && n > 0;   ) {
				if (sct.text[i++] == '\n') n--;
			}
			if ( i > b)
				s = s + sct.text[b:i];
			if (n == 0 || ln < sct.nrunes) return (n, s);
		}
		p -= sct.nrunes;
	}
	return (n, s);
}

sctput(scts: list of ref Section, pos: int, s: string): list of ref Section
{
	# There should be a hole to receive text
	if (scts == nil  && s != "") panic("sctput: scts is nil\n");
	sct := hd scts;
	l := len sct.text;
	if (sct.nrunes <= pos) {
		return sct :: sctput(tl scts, pos-sct.nrunes, s);
	}
	if (pos < l) {
		sctdump(scts, "panic");
		panic("sctput: overwriting");
	}
	if (pos == l) {
		if (sct.nrunes < l + len s) {
			sct.text += s[:sct.nrunes-l];
			return sct :: sctput(tl scts, 0, s[sct.nrunes-l:]);
		} 
		sct.text += s;	
		return sct :: tl scts;
	}
	nrunes := sct.nrunes;
	sct.nrunes = pos;
	if (nrunes < pos + len s)
		return	sct ::
			ref Section(nrunes-pos, s[:nrunes-pos]) ::
			sctput(tl scts, 0, s[nrunes-pos:]);
	return sct :: ref Section(nrunes-pos, s) :: tl scts;
}

sctmakeroom(scts: list of ref Section, pos: int, l: int): list of ref Section
{
	if (scts == nil) {
		if (pos) panic("sctmakeroom: beyond end of sections");
		return ref Section(l, nil) :: nil;
	}
	sct := hd scts;
	if (sct.nrunes < pos)
		return sct :: sctmakeroom(tl scts, pos-sct.nrunes, l);
	if (len sct.text <= pos) {
		# just add to the hole at end of section
		sct.nrunes += l;
		return sct :: tl scts;
	}
	if (pos == 0) {
		# text is non-nil!
		bsct := ref Section(l, nil);
		return bsct :: scts;
	}
	bsct := ref Section(pos + l, sct.text[0:pos]);
	esct := ref Section(sct.nrunes-pos, sct.text[pos:]);
	return bsct :: esct :: tl scts;
}

sctdelete(scts: list of ref Section, start, nbytes: int): list of ref Section
{
	if (nbytes == 0) return scts;
	if (scts == nil) panic("sctdelete: at eof");
	sct := hd scts;
	scts = tl scts;
	nrunes := sct.nrunes;
	if (start + nbytes < len sct.text) {
		sct.text = sct.text[0:start] + sct.text[start+nbytes:];
		sct.nrunes -= nbytes;
		return sct :: scts;
	}
	if (start < nrunes) {
		if (start > 0) {
			if (start < len sct.text)
				sct.text = sct.text[0:start];
			if (start + nbytes <= nrunes) {
				sct.nrunes -= nbytes;
				return sct :: scts;
			}
			sct.nrunes = start;
			return sct :: sctdelete(scts, 0, nbytes-nrunes+start);
		}
		if (nbytes < nrunes) {
			sct.text = "";
			sct.nrunes -= nbytes;
			return sct :: scts;
		}
		return sctdelete(scts, 0, nbytes - nrunes);
	}
	return sct :: sctdelete(scts, start - nrunes, nbytes);
}

grow(t: ref Text, at, l: int)
{
#	sctdump(t.sects, "grow, before");
	t.sects = sctmakeroom(t.sects, at, l);
	t.nrunes += l;
#	sctdump(t.sects, "grow, after");
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		fl := hd fls;
		if (at < fl.scope.first) fl.scope.first += l;
		if (at < fl.scope.last) fl.scope.last += l;
	}
}

findhole(t: ref Text): (int, int)
{
	for (fls := t.flayers; fls != nil; fls = tl fls) {
		(h, l) := scthole(t, (hd fls).scope.first);
		if (l > 0) return (h, l);
	}
	return (0, 0);
}

sctdump(scts: list of ref Section, s: string)
{
	fprint(ctxt.logfd, "Sctdump: %s\n", s);
	p := 0;
	while (scts != nil) {
		sct := hd scts; scts = tl scts;
		fprint(ctxt.logfd, "\tsct@%4d len=%4d len txt=%4d: %s\n",
			p, sct.nrunes, len sct.text, contract(sct.text));
		p += sct.nrunes;
	}
	fprint(ctxt.logfd, "\tend@%4d\n", p);
}