shithub: pplay

Download patch

ref: 3cf7eea579556c5ee9a91476cbd88d2e533cf42d
parent: 8574965a34feb65d5870e3fc9d24df1fcf3d0c1f
author: qwx <qwx@sciops.net>
date: Mon Jun 19 07:27:33 EDT 2023

lilu dallas mooltidot and editing fixes (mooltibuf next)

- fix insert/replace, crop; describe op with a comment
- fix copy/hold improper splits
- move all editing semantics to chunk
- don't assume static dot anywhere
- more descriptive debug fmt
- fix truncated ends for input buffers
- mandatory dot recalculation until api freezes
- leverage legalized 0-size chunks to simplify edits
- terminate readproc on error
- smooth transitions between chunks
- increase defensively athread stacksize incase Outsz increases

--- a/chunk.c
+++ b/chunk.c
@@ -10,7 +10,6 @@
 	usize bufsz;
 	Ref;
 };
-static Chunk *norris;
 
 // FIXME: crazy idea, multisnarf with addressable elements; $n registers; fork pplay to display them → ?
 
@@ -20,15 +19,14 @@
 	Chunk *p2;
 	Chunk *l;
 	Chunk *r;
+	Dot *dot;
 };
 static Op *opbuf, *ophead, *opend;
 static usize opbufsz;
 
 static struct{
-	Chunk *from;
-	usize foff;
-	Chunk *to;
-	usize toff;
+	Chunk *c;
+	Dot;
 } hold;
 
 int
@@ -39,8 +37,8 @@
 	d = va_arg(fmt->args, Dot*);
 	if(d == nil)
 		return fmtstrcpy(fmt, "[??:??:??:??]");
-	return fmtprint(fmt, "[from=%08zux cur=%08zux to=%08zux]",
-		d->from, d->pos, d->to);
+	return fmtprint(fmt, "[cur=%#p from=%08zux to=%08zux off=%08zux tot=%08zux]",
+		d->norris, d->from, d->to, d->off, d->totalsz);
 }
 
 int
@@ -51,7 +49,7 @@
 	c = va_arg(fmt->args, Chunk*);
 	if(c == nil)
 		return fmtstrcpy(fmt, "[]");
-	return fmtprint(fmt, "0x%08p:%08zux::0x%08p:0x%08p", c, c->len, c->left, c->right);
+	return fmtprint(fmt, "0x%08p N=%08zux →L=0x%08p ←R=0x%08p", c, c->len, c->left, c->right);
 }
 
 static void
@@ -83,7 +81,8 @@
 	vseprint(s, s+sizeof s, fmt, arg);
 	va_end(arg);
 	fprint(2, "%s", s);
-	printchunks(c == nil ? norris : c);
+	if(c != nil)
+		printchunks(c);
 }
 
 static Chunk *
@@ -95,8 +94,6 @@
 	c->left = c;
 	c->right = c;
 	c->b = b;
-	c->boff = 0;
-	c->len = b->bufsz;
 	incref(&b->Ref);
 	return c;
 }
@@ -213,26 +210,29 @@
 	return n;
 }
 
-void
-checksz(void)
+static Dot
+newdot(Dot *dp)
 {
-	usize n;
+	Dot d = {0};
+	Chunk *c;
 
-	n = chainlen(norris);
-	fprint(2, "totalsz %zd %Δ :: chainlen %zd\n", totalsz, &dot, n);
-	assert(n == totalsz);
-	assert(dot.from < dot.to);
-	assert(dot.from <= totalsz);
-	assert(dot.pos >= dot.from && dot.pos < dot.to);
+	d.norris = dp->norris;
+	d.totalsz = d.norris->len;
+	/* paranoia */
+	for(c=d.norris->right; c!=d.norris; c=c->right)
+		d.totalsz += c->len;
+	d.cur = d.from = dp->from < d.totalsz ? dp->from : 0;
+	d.to = d.totalsz;
+	return d;
 }
 
 Chunk *
-p2c(usize p, usize *off)
+p2c(usize p, usize *off, Dot *d)
 {
 	Chunk *c;
 
-	for(c=norris; p>=c->len; c=c->right){
-		if(c == norris->left){
+	for(c=d->norris; p>=c->len; c=c->right){
+		if(c == d->norris->left){
 			assert(p == c->len);
 			break;
 		}
@@ -253,21 +253,19 @@
 unpop(char *)
 {
 	Op *op;
+	Dot *d;
 
 	if(opend == opbuf || ophead == opend)
 		return 0;
 	op = ophead++;
+	d = op->dot;
 	dprint(op->p1, "cmd/unpop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
-		&dot, op->p1, op->p2, op->l, op->r);
-	totalsz += chainlen(op->l);
+		d, op->p1, op->p2, op->l, op->r);
 	linkchunk(op->p1->left, op->l);
 	unlink(op->p1, op->p2);
-	totalsz -= chainlen(op->p1);
-	if(norris == op->p1)
-		norris = op->l;
-	dot.from = dot.pos = 0;
-	dot.to = totalsz;
-	latchedpos = -1;
+	if(d->norris == op->p1)
+		d->norris = op->l;
+	*d = newdot(d);
 	return 1;
 }
 
@@ -275,26 +273,24 @@
 popop(char *)
 {
 	Op *op;
+	Dot *d;
 
 	if(ophead == opbuf)
 		return 0;
 	op = --ophead;
+	d = op->dot;
 	dprint(op->l, "cmd/pop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
-		&dot, op->p1, op->p2, op->l, op->r);
-	totalsz += chainlen(op->p1);
+		d, op->p1, op->p2, op->l, op->r);
 	linkchunk(op->l->left, op->p1);
 	unlink(op->l, op->r);
-	totalsz -= chainlen(op->l);
-	if(norris == op->l)
-		norris = op->p1;
-	dot.from = dot.pos = 0;
-	dot.to = totalsz;
-	latchedpos = -1;
+	if(d->norris == op->l)
+		d->norris = op->p1;
+	*d = newdot(d);
 	return 1;
 }
 
 static void
-pushop(Chunk *p1, Chunk *p2, Chunk *l, Chunk *r)
+pushop(Chunk *p1, Chunk *p2, Chunk *l, Chunk *r, Dot *d)
 {
 	Op *op;
 
@@ -311,164 +307,172 @@
 			forgetop(op);
 		memset(ophead, 0, (opend - ophead) * sizeof *ophead);
 	}
-	*ophead++ = (Op){p1, p2, l, r};
+	*ophead++ = (Op){p1, p2, l, r, d};
 	opend = ophead;
 }
 
+/* ..[p1]..[p2].. → ..[p1|l]..[r|p2].. → [l]..[r]  */
 void
-ccrop(usize from, usize to)
+ccrop(Dot *d)
 {
-	usize n, off;
+	usize foff, toff;
 	Chunk *p1, *p2, *l, *r;
 
-	assert(from < to && to <= totalsz);
-
-	p1 = p2c(from, &off);
-	if(p1->len - off >= to - from){
-		l = splitchunk(p1, off, off);
-		p2 = p1;
-		r = splitchunk(p2, off, off + to - from);
-		if(p1 != norris)
-			p1 = p1->left;
-		else
-			p2 = p2->right;
+	if(d->to == d->from){
+		fprint(2, "empty crop\n");
+		return;
+	}
+	assert(d->from < d->to && d->to <= d->totalsz);
+	p1 = p2c(d->from, &foff, d);
+	p2 = p2c(d->to, &toff, d);
+	if(p1 == p2){
+		l = splitchunk(p1, foff, toff);
+		r = l;
 	}else{
-		p1 = p2c(from, &off);
-		l = splitchunk(p1, off, p1->len);
-		p2 = p2c(to, &off);
-		r = splitchunk(p2, 0, off);
+		l = splitchunk(p1, foff, p1->len);
+		r = splitchunk(p2, 0, toff);
+		if(p1->right != p2)
+			linkchunk(p2->left, r);
+		else
+			linkchunk(l, r);
 	}
+	dprint(d->norris, "ccrop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n", d, p1, p2, l, r);
 	linkchunk(p1, l);
-	linkchunk(p2->left, r);
 	unlink(p2, p1);
-	n = chainlen(l);
-	totalsz = n;
-	pushop(p1, p2, l, r);
-	norris = l;
-	dot.from = dot.pos = 0;
-	dot.to = n;
-	latchedpos = -1;
+	pushop(p1, p2, l, r, d);
+	d->norris = l;
+	*d = newdot(d);
 }
 
+/* [p1]..[p2] → [l|p1]..[p2|r] → [l]..c..[r]  */
 static int
-creplace(usize from, usize to, Chunk *c)
+creplace(Dot *d, Chunk *c)
 {
-	usize n, off;
+	usize foff, toff;
 	Chunk *p1, *p2, *l, *r;
 
-	assert(from < to && to <= totalsz);
-	p1 = p2c(from, &off);
-	l = splitchunk(p1, 0, off);
-	p2 = p2c(to, &off);
-	r = splitchunk(p2, off, p2->len);
-	linkchunk(c, r);
+	assert(d->from <= d->totalsz && d->to - d->from > 0);
+	p1 = p2c(d->from, &foff, d);
+	p2 = p2c(d->to, &toff, d);
+	l = splitchunk(p1, 0, foff);
+	r = splitchunk(p2, toff, p2->len);
 	linkchunk(l, c);
-	n = chainlen(l);
-	totalsz += n;
+	linkchunk(c, r);
+	dprint(d->norris, "creplace dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n", d, p1, p2, l, r);
 	linkchunk(p1->left, l);
 	unlink(p1, p2);
-	totalsz -= chainlen(p1);
-	pushop(p1, p2, l, r);
-	if(p1 == norris)
-		norris = l;
-	dot.from = dot.pos = from;
-	dot.to = from + n;
-	latchedpos = -1;
+	pushop(p1, p2, l, r, d);
+	if(p1 == d->norris)
+		d->norris = l;
+	*d = newdot(d);
 	return 0;
 }
 
-// FIXME: use a specific Dot (prep for multibuf); generalize
+/* ..[p1].. → ..[l|r].. → ..[l|c|r].. */
 static int
-cinsert(usize pos, Chunk *c)
+cinsert(Dot *d, Chunk *c)
 {
-	usize n, off;
-	Chunk *p, *l, *r;
+	usize off;
+	Chunk *p1, *l, *r;
 
-	assert(pos <= totalsz);
-	p = p2c(pos, &off);
-	l = splitchunk(p, 0, off);
-	r = splitchunk(p, off, p->len);
-	linkchunk(c, r);
+	assert(d->off != -1ULL);
+	p1 = p2c(d->off, &off, d);
+	l = splitchunk(p1, 0, off);
+	r = splitchunk(p1, off, p1->len);
 	linkchunk(l, c);
-	n = chainlen(l);
-	totalsz += n;
-	linkchunk(p->left, l);
-	unlink(p, p);
-	totalsz -= chainlen(p);
-	pushop(p, p, l, r);
-	if(p == norris)
-		norris = l;
-	dot.from = dot.pos = pos;
-	dot.to = pos + n;
-	latchedpos = -1;
+	linkchunk(c, r);
+	dprint(d->norris, "cinsert dot=%Δ P [%χ] LR [%χ][%χ]\n", d, p1, l, r);
+	linkchunk(p1->left, l);
+	unlink(p1, p1);
+	pushop(p1, p1, l, r, d);
+	if(p1 == d->norris)
+		d->norris = l;
+	d->from = d->off;
+	d->to = d->totalsz;
+	*d = newdot(d);
 	return 0;
 }
 
 int
-cpaste(usize from, usize to)
+cpaste(Dot *d)
 {
-	Chunk *p1, *p2, *l, *r;
+	Chunk *c;
 
-	if(hold.from == nil || hold.to == nil){
+	if(hold.c == nil){
 		werrstr("cpaste: nothing to paste");
 		return -1;
 	}
-	p1 = hold.from;
-	p2 = hold.to;
-	if(p1 == p2)
-		l = splitchunk(p1, hold.foff, hold.toff);
-	else{
-		l = splitchunk(p1, hold.foff, p1->len);
-		r = splitchunk(p2, 0, hold.toff);
-		if(p1->right != p2)
-			linkchunk(l, clone(p1->right, p2->left));
-		linkchunk(l->left, r);
-	}
-	return from == to ? cinsert(from, l) : creplace(from, to, l);
+	dprint(d->norris, "cpaste dot=%Δ hold=%Δ\n", d, &hold.Dot);
+	c = clone(hold.c, hold.c->left);
+	return d->off == -1ULL ? creplace(d, c) : cinsert(d, c);
 }
 
 void
-ccopy(usize from, usize to)
+chold(Chunk *c, Dot *d)
 {
-	hold.from = p2c(from, &hold.foff);
-	hold.to = p2c(to, &hold.toff);
+	if(hold.c != nil)
+		freechain(hold.c);
+	hold.c = c;
+	hold.Dot = *d;
 }
 
-void
-chold(Chunk *c)
+/* [p1]..[x]..[p2] → [p1|l]..[x]..[r|p2] → hold = [l]..[x]..[r] */
+Chunk *
+ccopy(Dot *d)
 {
-	hold.from = hold.to = c;
-	hold.foff = hold.toff = 0;
+	usize foff, toff;
+	Chunk *p1, *p2, *l, *r;
+
+	if(hold.c != nil)
+		freechain(hold.c);
+	p1 = p2c(d->from, &foff, d);
+	p2 = p2c(d->to, &toff, d);
+	if(p1 == p2){
+		l = splitchunk(p1, foff, toff);
+		r = nil;
+	}else{
+		l = splitchunk(p1, foff, p1->len);
+		r = splitchunk(p2, 0, toff);
+		if(p1->right != p2)
+			linkchunk(l, clone(p1->right, p2->left));
+		linkchunk(l->left, r);
+	}
+	dprint(d->norris, "ccopy dot=%Δ\n\tP\t[ %χ ]\t[ %χ ]\n\tLR:\t[ %χ ]\t[ %χ ]\n",
+		d, p1, p2, l, p1 == p2 ? l : r);
+	hold.c = l;
+	hold.Dot = *d;
+	return hold.c;
 }
 
+/* [p1]..[x]..[p2] → [l|p1]..[x]..[p2|r] → [l][r] */
 void
-ccut(usize from, usize to)
+ccut(Dot *d)
 {
-	usize n;
+	usize off;
 	Chunk *p1, *p2, *l, *r;
 
-	if(from - to == totalsz){
+	if(d->from - d->to == d->totalsz){
 		fprint(2, "ccut: not cutting entire buffer\n");
 		return;
 	}
-	assert(from < to && to <= totalsz);
-	ccopy(from, to);
-	p1 = hold.from;
-	p2 = hold.to;
-	l = splitchunk(p1, 0, hold.foff);
-	r = splitchunk(p2, hold.toff, p2->len);
+	assert(d->from < d->to && d->to <= d->totalsz);
+	ccopy(d);
+	p1 = p2c(d->from, &off, d);
+	l = splitchunk(p1, 0, off);
+	if(d->from == d->to)
+		p2 = p1;
+	else
+		p2 = p2c(d->to, &off, d);
+	r = splitchunk(p2, off, p2->len);
 	linkchunk(l, r);
-	n = chainlen(l);
-	totalsz += n;
+	dprint(d->norris, "ccut dot=%Δ\n\tP\t[ %χ ]\t[ %χ ]\n\tLR:\t[ %χ ]\t[ %χ ]\n",
+		d, p1, p2, l, r);
 	linkchunk(p1->left, l);
 	unlink(p1, p2);
-	totalsz -= chainlen(p1);
-	pushop(p1, p2, l, r);
-	if(p1 == norris)
-		norris = l;
-	dot.from = dot.pos = from;
-	dot.to = totalsz;
-	latchedpos = -1;
+	pushop(p1, p2, l, r, d);
+	if(p1 == d->norris)
+		d->norris = l;
+	*d = newdot(d);
 }
 
 uchar *
@@ -477,12 +481,12 @@
 	usize n, off;
 	Chunk *c;
 
-	if(d->pos >= totalsz){
+	if(d->cur >= d->totalsz){
 		werrstr("out of bounds");
 		*have = 0;
 		return nil;
 	}
-	c = p2c(d->pos, &off);
+	c = p2c(d->cur, &off, d);
 	n = c->len - off;
 	*have = want > n ? n : want;
 	return c->b->buf + c->boff + off;
@@ -489,7 +493,7 @@
 }
 
 Chunk *
-chunkfile(int fd)
+loadfile(int fd, Dot *d)
 {
 	int n;
 	Chunk *c;
@@ -499,7 +503,7 @@
 	b = c->b;
 	for(;;){
 		if(b->bufsz - c->len < Chunksz){
-			b->buf = erealloc(c->b->buf, 2 * c->b->bufsz, c->b->bufsz);
+			b->buf = erealloc(b->buf, 2 * b->bufsz, b->bufsz);
 			b->bufsz *= 2;
 		}
 		if((n = readn(fd, b->buf + c->len, Chunksz)) < Chunksz)
@@ -508,11 +512,13 @@
 		yield();
 	}
 	if(n < 0){
-		fprint(2, "chunkfile: %r\n");
+		fprint(2, "loadfile: %r\n");
 		freechunk(c);
 		return nil;
-	}else if(c->len == 0){
-		fprint(2, "chunkfile: nothing read\n");
+	}
+	c->len += n;
+	if(c->len == 0){
+		fprint(2, "loadfile: nothing read\n");
 		freechunk(c);
 		return nil;
 	}else if(c->len < b->bufsz){
@@ -519,14 +525,7 @@
 		b->buf = erealloc(b->buf, c->len, b->bufsz);
 		b->bufsz = c->len;
 	}
+	d->norris = c;
+	*d = newdot(d);
 	return c;
-}
-
-void
-initbuf(Chunk *c)
-{
-	norris = c;
-	totalsz = chainlen(c);
-	dot.pos = dot.from = 0;
-	dot.to = totalsz;
 }
--- a/cmd.c
+++ b/cmd.c
@@ -4,9 +4,9 @@
 #include "dat.h"
 #include "fns.h"
 
-Dot dot;
-usize totalsz;
 int treadsoftly;
+usize ndots;
+Dot *current, *dots;
 
 static int epfd[2];
 
@@ -13,20 +13,13 @@
 static int
 paste(char *)
 {
-	usize from, to;
-
-	from = dot.from;
-	to = dot.to;
-	if(latchedpos >= 0 && dot.from == 0 && dot.to == totalsz)
-		to = from = latchedpos;
-	latchedpos = -1;
-	return cpaste(from, to) == 0 ? 1 : -1;
+	return cpaste(current) == 0 ? 1 : -1;
 }
 
 static int
 copy(char *)
 {
-	ccopy(dot.from, dot.to);
+	ccopy(current);
 	return 0;
 }
 
@@ -33,12 +26,12 @@
 static vlong
 cut(char *)
 {
-	dprint(nil, "cmd/cut %Δ\n", &dot);
-	if(dot.from == 0 && dot.to == totalsz){
+	dprint(nil, "cmd/cut %Δ\n", current);
+	if(current->from == 0 && current->to == current->totalsz){
 		werrstr("cut: can't cut entire buffer");
 		return -1;
 	}
-	ccut(dot.from, dot.to);
+	ccut(current);
 	return 1;
 }
 
@@ -45,8 +38,8 @@
 static int
 crop(char *)
 {
-	dprint(nil, "cmd/crop %Δ\n", &dot);
-	ccrop(dot.from, dot.to);
+	dprint(nil, "cmd/crop %Δ\n", current);
+	ccrop(current);
 	return 1;
 }
 
@@ -58,11 +51,11 @@
 	usize n, m, k;
 	Dot d;
 
-	d = dot;
-	d.pos = d.from;
+	d = *current;
+	d.cur = d.from;
 	if((nio = iounit(fd)) == 0)
 		nio = 8192;
-	for(m=d.to-d.from, b=(uchar*)&d; m>0; m-=n, d.pos+=n){
+	for(m=d.to-d.from, b=(uchar*)&d; m>0; m-=n, d.cur+=n){
 		k = nio < m ? nio : m;
 		if((b = getslice(&d, k, &k)) == nil || k <= 0){
 			fprint(2, "writebuf: couldn\'t snarf: %r\n");
@@ -103,22 +96,23 @@
 rproc(void *efd)
 {
 	int fd;
-	Dot d;
+	Dot d, cd;
 	Chunk *c;
 
-	d = dot;
+	d = *current;
 	treadsoftly++;
 	fd = (intptr)efd;
-	if((c = chunkfile(fd)) == nil){
+	if((c = loadfile(fd, &cd)) == nil){
 		treadsoftly = 0;
-		threadexits("failed reading from pipe: %r");
+		fprint(2, "failed reading from pipe: %r");
+		threadexits("read error");
 	}
 	close(fd);
 	qlock(&lsync);
-	dot.from = d.from;
-	dot.to = d.to;
-	chold(c);
-	paste(nil);
+	chold(c, &d);
+	*current = d;
+	if(paste(nil) < 0)
+		fprint(2, "paste: %r\n");
 	qunlock(&lsync);
 	treadsoftly--;
 	redraw(0);
@@ -225,6 +219,7 @@
 			break;
 		s += n;
 	}
+	dprint(current->norris, "current dot=%Δ\n", current);
 	switch(r){
 	case '<': x = pipefrom(s); break;
 	case '^': x = pipethrough(s); break;
@@ -241,8 +236,7 @@
 	case 'x': x = crop(s); break;
 	default: werrstr("unknown command %C", r); x = -1; break;
 	}
-	if(debug)
-		checksz();
+	dprint(current->norris, "final dot=%Δ\n", current);
 	return x;
 }
 
@@ -251,28 +245,17 @@
 {
 	usize m;
 
-	assert(d->pos >= d->from && d->pos <= d->to);
+	assert(d->cur >= d->from && d->cur <= d->to);
 	while(n > 0){
-		m = d->to - d->pos > n ? n : d->to - d->pos;
+		m = d->to - d->cur > n ? n : d->to - d->cur;
 		n -= m;
-		d->pos += m;
-		if(d->pos == d->to)
-			d->pos = d->from;
+		d->cur += m;
+		if(d->cur == d->to)
+			d->cur = d->from;
 	}
 	return 0;
 }
 
-int
-loadin(int fd)
-{
-	Chunk *c;
-
-	if((c = chunkfile(fd)) == nil)
-		sysfatal("loadin: %r");
-	initbuf(c);
-	return 0;
-}
-
 static void
 catch(void *, char *msg)
 {
@@ -281,8 +264,15 @@
 	noted(NDFLT);
 }
 
-void
-initcmd(void)
+int
+initcmd(int fd)
 {
-	notify(catch);
+	Dot d;
+
+	if(loadfile(fd, &d) == nil)
+		sysfatal("initcmd: %r");
+	dots = emalloc(++ndots * sizeof *dots);
+	dots[0] = d;
+	current = dots;
+	return 0;
 }
--- a/dat.h
+++ b/dat.h
@@ -19,13 +19,16 @@
 	Chunk *right;
 };
 extern struct Dot{
-	usize pos;
+	usize cur;
 	usize from;
 	usize to;
+	usize off;
+	usize totalsz;
+	Chunk *norris;
 };
-extern Dot dot;
-extern vlong latchedpos;
-extern usize totalsz;
+extern Dot *dots, *current;
+extern usize ndots;
+
 extern int treadsoftly;
 
 extern QLock lsync;
--- a/draw.c
+++ b/draw.c
@@ -7,7 +7,6 @@
 
 QLock lsync;
 int debugdraw;
-vlong latchedpos;
 
 enum{
 	Cbg,
@@ -62,7 +61,7 @@
 	usize p;
 
 	p = va_arg(fmt->args, usize);
-	if(p > totalsz)
+	if(p > current->totalsz)
 		return fmtstrcpy(fmt, "-∞");
 	b2t(p, &th, &tm, &ts, &tμ);
 	return fmtprint(fmt, "%02d:%02d:%02d.%03d (%zd)",
@@ -89,7 +88,7 @@
 	usize p, off;
 	Chunk *c;
 
-	c = p2c(views, &off);
+	c = p2c(views, &off, current);
 	for(p=views-off; p<viewe; p+=c->len, c=c->right){
 		if(p == 0)
 			continue;
@@ -114,9 +113,10 @@
 		lockdisplay(display);
 		draw(viewbg, viewbg->r, col[Cbg], nil, ZP);
 		unlockdisplay(display);
+		d = *current;
 		d.from = 0;
-		d.pos = views;
-		d.to = totalsz;
+		d.cur = views;
+		d.to = d.totalsz;
 		m = viewe - views;
 		x = 0;
 		qlock(&lsync);
@@ -134,7 +134,7 @@
 						fprint(2, "getslice: %r\n");
 					goto end;
 				}
-				d.pos += k;
+				d.cur += k;
 				e = p + k;
 				while(p < e){
 					s = (s16int)(p[1] << 8 | p[0]);
@@ -163,7 +163,7 @@
 			if(stereo)
 				draw(viewbg, r, col[Csamp], nil, ZP);
 			unlockdisplay(display);
-			x = (d.pos - views) / T;
+			x = (d.cur - views) / T;
 			if(x % 320 == 0)
 				update();
 		}
@@ -178,14 +178,14 @@
 	char s[256];
 	Point p;
 
-	seprint(s, s+sizeof s, "T %zd @ %τ", T / Sampsz, dot.pos);
+	seprint(s, s+sizeof s, "T %zd @ %τ", T / Sampsz, current->cur);
 	p = string(screen, statp, col[Ctext], ZP, font, s);
-	if(dot.from > 0 || dot.to < totalsz){
-		seprint(s, s+sizeof s, " ↺ %τ - %τ (%τ)", dot.from, dot.to, dot.to - dot.from);
+	if(current->from > 0 || current->to < current->totalsz){
+		seprint(s, s+sizeof s, " ↺ %τ - %τ", current->from, current->to);
 		p = string(screen, p, col[Cloop], ZP, font, s);
 	}
-	if(latchedpos >= 0){
-		seprint(s, s+sizeof s, " ‡ %τ", (usize)latchedpos);
+	if(current->off != current->from){
+		seprint(s, s+sizeof s, " ‡ %τ", current->off);
 		p = string(screen, p, col[Cins], ZP, font, s);
 	}
 }
@@ -196,10 +196,10 @@
 	draw(view, view->r, viewbg, nil, ZP);
 	if(debugdraw)
 		drawchunks();
-	drawpos(dot.from, col[Cloop]);
-	drawpos(dot.to, col[Cloop]);
-	if(latchedpos >= 0)
-		drawpos(latchedpos, col[Cins]);
+	drawpos(current->from, col[Cloop]);
+	drawpos(current->to, col[Cloop]);
+	if(current->off != current->from)
+		drawpos(current->from, col[Cins]);
 }
 
 void
@@ -208,7 +208,7 @@
 	int x;
 	usize p;
 
-	p = dot.pos;
+	p = current->cur;
 	lockdisplay(display);
 	drawview();
 	draw(screen, screen->r, view, nil, ZP);
@@ -233,7 +233,7 @@
 		z = zoom / pow(2, -Δz);
 	else
 		z = zoom * pow(2, Δz);
-	if(z < 1.0 || z > (totalsz / Sampsz) / Dx(screen->r))
+	if(z < 1.0 || z > (current->totalsz / Sampsz) / Dx(screen->r))
 		return;
 	zoom = z;
 	redraw(0);
@@ -245,8 +245,8 @@
 	if(from < 0)
 		from = 0;
 	from &= ~3;
-	if(to >= totalsz)
-		to = totalsz;
+	if(to >= current->totalsz)
+		to = current->totalsz;
 	to &= ~3;
 	if((to - from) / Sampsz < Dx(screen->r)){
 		werrstr("range too small");
@@ -254,7 +254,7 @@
 	}
 	views = from;
 	viewe = to;
-	zoom = (double)totalsz / (to - from);
+	zoom = (double)current->totalsz / (to - from);
 	redraw(0);
 	return 0;
 }
@@ -290,22 +290,21 @@
 {
 	assert((from & 3) == 0);
 	assert((to & 3) == 0);
-	dot.from = from;
-	dot.to = to;
-	if(dot.pos < from || dot.pos >= to)
-		dot.pos = from;
-	latchedpos = -1;
+	current->from = from;
+	current->to = to;
+	if(current->cur < from || current->cur >= to)
+		current->cur = from;
+	current->off = -1ULL;
 }
 
 static int
 setcur(usize off)
 {
-	if(off < dot.from || off > dot.to - Outsz){
+	if(off < current->from || off > current->to - Outsz){
 		werrstr("cannot jump outside of loop bounds\n");
 		return -1;
 	}
-	dot.pos = off;
-	latchedpos = off;
+	current->off = current->cur = off;
 	update();
 	return 0;
 }
@@ -315,12 +314,12 @@
 {
 	off *= T;
 	off += views;
-	if(off < 0 || off > totalsz)
+	if(off < 0 || off > current->totalsz)
 		return;
-	if(off < dot.pos)
-		setrange(off, dot.to);
+	if(off < current->cur)
+		setrange(off, current->to);
 	else
-		setrange(dot.from, off);
+		setrange(current->from, off);
 	update();
 }
 
@@ -342,7 +341,7 @@
 	int x;
 	Rectangle viewr;
 
-	x = screen->r.min.x + (dot.pos - views) / T;
+	x = screen->r.min.x + (current->cur - views) / T;
 	viewr = rectsubpt(screen->r, screen->r.min);
 	statp = screen->r.min;
 	if(stereo)
@@ -367,11 +366,11 @@
 	usize span;
 
 	lockdisplay(display);
-	T = (vlong)(totalsz / zoom / Dx(screen->r)) & ~3;
+	T = (vlong)(current->totalsz / zoom / Dx(screen->r)) & ~3;
 	if(T == 0)
 		T = 4;
 	span = Dx(screen->r) * T;
-	viewmax = totalsz - span;
+	viewmax = current->totalsz - span;
 	if(views > viewmax)
 		views = viewmax;
 	viewe = views + span;
--- a/fns.h
+++ b/fns.h
@@ -1,17 +1,15 @@
 void	dprint(Chunk*, char*, ...);
-void	checksz(void);
 void	freechain(Chunk*);
 int	unpop(char*);
 int	popop(char*);
-int	cpaste(usize, usize);
-void	ccopy(usize, usize);
-void	chold(Chunk*);
-void	ccut(usize, usize);
-void	ccrop(usize, usize);
-Chunk*	chunkfile(int);
-void	initbuf(Chunk*);
+int	cpaste(Dot*);
+Chunk*	ccopy(Dot*);
+void	chold(Chunk*, Dot*);
+void	ccut(Dot*);
+void	ccrop(Dot*);
+Chunk*	loadfile(int, Dot*);
 int	cmd(char*);
-void	initcmd(void);
+int	initcmd(int);
 void	update(void);
 void	setzoom(int, int);
 int	zoominto(vlong, vlong);
@@ -24,11 +22,10 @@
 void	redraw(int);
 void	initdrw(int);
 int	advance(Dot*, usize);
-Chunk*	p2c(usize, usize*);
+Chunk*	p2c(usize, usize*, Dot*);
 int	setpos(usize);
 uchar*	getslice(Dot*, usize, usize*);
 vlong	getbuf(Dot, usize, uchar*, usize);
-int	loadin(int);
 int	χfmt(Fmt*);
 int	Δfmt(Fmt*);
 int	τfmt(Fmt*);
--- a/pplay.c
+++ b/pplay.c
@@ -20,27 +20,28 @@
 static void
 athread(void *)
 {
-	int m, nerr;
-	uchar *b;
-	usize n;
+	int nerr;
+	uchar *b, *bp, buf[Outsz];
+	usize n, m;
 
 	nerr = 0;
 	for(;;){
 		if(afd < 0 || nerr > 10)
 			return;
-		for(m=Outsz; m>0; m-=n){
-			if((b = getslice(&dot, Outsz, &n)) == nil || n <= 0){
+		for(bp=buf, m=sizeof buf; bp<buf+sizeof buf; bp+=n, m-=n){
+			if((b = getslice(current, m, &n)) == nil || n <= 0){
 				fprint(2, "athread: %r\n");
 				nerr++;
 				goto skip;
 			}
-			if(write(afd, b, n) != n){
-				fprint(2, "athread write: %r\n");
-				threadexits("write");
-			}
+			memcpy(bp, b, n);
+			advance(current, n);
 		}
+		if(write(afd, buf, sizeof buf) != sizeof buf){
+			fprint(2, "athread write: %r\n");
+			threadexits("write");
+		}
 		nerr = 0;
-		advance(&dot, n);
 		update();
 skip:
 		yield();
@@ -58,7 +59,7 @@
 			play = 0;
 			return;
 		}
-		if(threadcreate(athread, nil, mainstacksize) < 0)
+		if(threadcreate(athread, nil, 2*mainstacksize) < 0)
 			sysfatal("threadcreate: %r");
 	}else{
 		if(!cat)
@@ -103,13 +104,13 @@
 	case 'c': cat = 1; break;
 	default: usage();
 	}ARGEND
-	if((fd = *argv != nil ? open(*argv, OREAD) : 0) < 0)
-		sysfatal("open: %r");
 	fmtinstall(L'Δ', Δfmt);
 	fmtinstall(L'χ', χfmt);
 	fmtinstall(L'τ', τfmt);
-	if(loadin(fd) < 0)
-		sysfatal("inittrack: %r");
+	if((fd = *argv != nil ? open(*argv, OREAD) : 0) < 0)
+		sysfatal("open: %r");
+	if(initcmd(fd) < 0)
+		sysfatal("init: %r");
 	close(fd);
 	initdrw(notriob);
 	if((kc = initkeyboard(nil)) == nil)
@@ -123,7 +124,6 @@
 		{kc->c, &r, CHANRCV},
 		{nil, nil, CHANEND}
 	};
-	initcmd();
 	if(setpri(13) < 0)
 		fprint(2, "setpri: %r\n");
 	toggleplay();
@@ -154,10 +154,10 @@
 			case 'D': debug ^= 1; debugdraw ^= 1; break;
 			case 'S': stereo ^= 1; redraw(1); break;
 			case ' ': toggleplay(); break;
-			case 'b': setjump(dot.from); break;
-			case Kesc: setrange(0, totalsz); update(); break;
-			case '\n': zoominto(dot.from, dot.to); break;
-			case 'z': zoominto(0, totalsz); break;
+			case 'b': setjump(current->from); break;
+			case Kesc: setrange(0, current->totalsz); update(); break;
+			case '\n': zoominto(current->from, current->to); break;
+			case 'z': zoominto(0, current->totalsz); break;
 			case '-': setzoom(-1, 0); break;
 			case '=': setzoom(1, 0); break;
 			case '_': setzoom(-1, 1); break;