git: purgatorio

ref: 1f6de2fe3823cc6e749b8254187e81e20589bae8
dir: /appl/wm/vixen/vixen/buffers.b/

View raw version
Pos: adt {
	l, c:	int;  # line, column

	cmp:		fn(a, b: Pos): int;
	order:		fn(a, b: Pos): (Pos, Pos);
	eq:		fn(a, b: Pos): int;
	parse:		fn(s: string): Pos;
	text:		fn(p: self Pos): string;
};
nullpos: Pos;

Colkeep, Colstart, Colfirstnonblank, Colend, Colpastnewline: con -iota-1;  # mvline's colmv
Cursor: adt {
	b:	ref Buf;
	o:	int;	# offset into buf.  o can be set to length of buffer
	pos:	Pos;

	clone:		fn(c: self ref Cursor): ref Cursor;
	char:		fn(c: self ref Cursor): int;
	charprev:	fn(c: self ref Cursor): int;
	prev,
	next:		fn(c: self ref Cursor): int;
	walk:		fn(c: self ref Cursor, rev: int): int;

	mvchar,
	mvcol:		fn(c: self ref Cursor, col: int): ref Cursor;
	mvline:		fn(c: self ref Cursor, rel: int, colmv: int): ref Cursor;
	mvpos:		fn(c: self ref Cursor, p: Pos): ref Cursor;
	mvlineend:	fn(c: self ref Cursor, nl: int): ref Cursor;
	mvword,
	mvwordend:	fn(c: self ref Cursor, capital: int, n: int): ref Cursor;
	mvfirst:	fn(c: self ref Cursor): ref Cursor;
	mvsentence:	fn(c: self ref Cursor, prev: int): ref Cursor;
	mvparagraph:	fn(c: self ref Cursor, prev: int): ref Cursor;
	mvskip:		fn(c: self ref Cursor, cl: string): ref Cursor;

	word:		fn(c: self ref Cursor): (ref Cursor, ref Cursor);
	pathpattern:	fn(c: self ref Cursor, search: int): (ref Cursor, ref Cursor);
	findchar:	fn(c: self ref Cursor, cl: string, rev: int): ref Cursor;
	findstr:	fn(c: self ref Cursor, s: string, rev: int): ref Cursor;
	findlinechar:	fn(c: self ref Cursor, x: int, rev: int): ref Cursor;

	cmp:		fn(a, b: ref Cursor): int;
	order:		fn(a, b: ref Cursor): (ref Cursor, ref Cursor);
	diff:		fn(a, b: ref Cursor): int;
	linelength:	fn(c: self ref Cursor, nl: int): int;
	text:		fn(c: self ref Cursor): string;
};

Buf: adt {
	s:	string;
	nlines:	int;

	new:		fn(): ref Buf;
	get:		fn(b: self ref Buf, f, t: ref Cursor): string;
	del:		fn(b: self ref Buf, f, t: ref Cursor): string;
	ins:		fn(b: self ref Buf, w: ref Cursor, s: string): ref Cursor;
	set:		fn(b: self ref Buf, s: string);
	cursor:		fn(b: self ref Buf, o: int): ref Cursor;
	pos:		fn(b: self ref Buf, pos: Pos): ref Cursor;
	lines:		fn(b: self ref Buf): int;
	chars:		fn(b: self ref Buf): int;
	end:		fn(b: self ref Buf): ref Cursor;
	str:		fn(b: self ref Buf): string;
};


checkcursor(c: ref Cursor)
{
	if(c.o < 0 || c.o > len c.b.s)
		raise "cursor.o invalid";
	cc := ref *c;
	cc.o = 0;
	cc.pos = Pos (1, 0);
	while(c.o != cc.o)
		cc.next();
	if(!Pos.eq(c.pos, cc.pos))
		raise sprint("cursor has wrong pos, got %s, pos should be %s", c.text(), cc.pos.text());
}

Pos.cmp(a, b: Pos): int
{
	if(a.l < b.l || a.l == b.l && a.c < b.c) return -1;
	if(a.l > b.l || a.l == b.l && a.c > b.c) return 1;
	return 0;
}

Pos.order(a, b: Pos): (Pos, Pos)
{
	if(a.l > b.l || (a.l == b.l && a.c > b.c))
		return (b, a);
	return (a, b);
}

Pos.eq(a, b: Pos): int
{
	return a.l == b.l && a.c == b.c;
}

Pos.parse(s: string): Pos
{
	(a, b) := str->splitstrl(s, ".");
	if(b != nil)
		b = b[1:];
	return Pos (int a, int b);
}

Pos.text(p: self Pos): string
{
	return sprint("%d.%d", p.l, p.c);
}


Cursor.clone(c: self ref Cursor): ref Cursor
{
	return ref *c;
}

Cursor.char(c: self ref Cursor): int
{
	if(c.o < 0 || c.o >= len c.b.s)
		return -1;
	return c.b.s[c.o];
}

Cursor.charprev(c: self ref Cursor): int
{
	o := c.o-1;
	if(o < 0 || o >= len c.b.s)
		return -1;
	return c.b.s[o];
}

# move back one char in the buffer and return it.
# if already at start, return -1.
Cursor.prev(c: self ref Cursor): int
{
	if(c.o <= 0)
		return -1;
	r := c.b.s[--c.o];
	--c.pos.c;
	if(r == '\n') {
		--c.pos.l;
		c.pos.c = linelength(c, c.o);
	} else if(c.pos.c < 0)
		raise "bogus col";
	return r;
}

# return length of line without newline, don't use c.pos.c, it may be wrong
linelength(c: ref Cursor, o: int): int
{
	for(so := o; so > 0 && c.b.s[so-1] != '\n'; --so)
		{}
	for(eo := o; eo < len c.b.s && c.b.s[eo] != '\n'; ++eo)
		{}
	return eo-so;
}


# move cursor forward, return the character under it.
# if already under last character, we move forward, but return -1.
Cursor.next(c: self ref Cursor): int
{
	if(c.o >= len c.b.s)
		return -1;
	x := c.b.s[c.o];
	++c.o;
	++c.pos.c;
	if(x == '\n') {
		++c.pos.l;
		c.pos.c = 0;
	}
	if(c.o >= len c.b.s)
		return -1;
	return c.b.s[c.o];
}

Cursor.walk(c: self ref Cursor, rev: int): int
{
	if(rev)
		return c.prev();
	return c.next();
}

# move cursor by 'rel' characters, staying on the same line.
Cursor.mvchar(cc: self ref Cursor, rel: int): ref Cursor
{
	c := cc.clone();
	while(rel > 0) {
		x := c.char();
		if(x < 0 || x == '\n')
			break;
		c.next();
		--rel;
	}
	while(rel < 0) {
		x := c.charprev();
		if(x < 0 || x == '\n')
			break;
		c.prev();
		++rel;
	}
	checkcursor(c);
	return c;
}

# move cursor by 'rel' lines, try to keep cursor on same col.
Cursor.mvline(c: self ref Cursor, rel: int, colmv: int): ref Cursor
{
	c = c.clone();
	col := 0;
	if(colmv == Colkeep)
		col = c.pos.c;
	else if(colmv >= 0)
		col = colmv;
	c = c.mvpos(Pos (max(1, c.pos.l+rel), col));
	case colmv {
	Colkeep =>	{}
	Colstart =>	{}
	Colfirstnonblank =>	c = c.mvfirst();
	Colend =>		c = c.mvlineend(0);
	Colpastnewline =>	c = c.mvlineend(1);
	* =>
		if(colmv < 0)
			raise "bad colmv";
	}
	return c;
}

# move to column on current line, moving to end if col is past end of line
Cursor.mvcol(c: self ref Cursor, col: int): ref Cursor
{
	if(col < 0)
		col = 0;
	if(col > c.pos.c)
		col = min(c.linelength(0), col);
	cc := ref Cursor (c.b, c.o+col-c.pos.c, Pos (c.pos.l, col));
	checkcursor(cc);
	return cc;
}

Cursor.mvpos(cc: self ref Cursor, p: Pos): ref Cursor
{
	return cc.b.pos(p);
}

Cursor.mvlineend(c: self ref Cursor, nl: int): ref Cursor
{
	c = c.clone();
	x := c.char();
	while(x >= 0 && x != '\n')
		x = c.next();
	if(nl && x == '\n')
		c.next();
	return c;
}

Cursor.cmp(a, b: ref Cursor): int
{
	if(a.o == b.o) return 0;
	if(a.o < b.o) return -1;
	return 1;
}

Cursor.order(a, b: ref Cursor): (ref Cursor, ref Cursor)
{
	if(a.o > b.o)
		return (b, a);
	return (a, b);
}

Cursor.diff(a, b: ref Cursor): int
{
	return b.o-a.o;
}

# return length of line, including newline if present and nl is set
Cursor.linelength(cc: self ref Cursor, nl: int): int
{
	if(nl)
		nl = 1;
	# find newline or end of file
	c := cc.clone();
	x := c.char();
	for(;;) {
		if(x < 0)
			return c.pos.c;
		if(x == '\n')
			return c.pos.c+nl;
		x = c.next();
	}
}

whitespace: con " \t\n";
interpunction: con "!\"#$%&'()*+,./:;<=>?@\\]^_`{|}~-[";
whitespaceinterpunction: con " \t\n!\"#$%&'()*+,./:;<=>?@\\]^_`{|}~-[";
Cursor.mvword(cc: self ref Cursor, capital: int, n: int): ref Cursor
{
	c := cc.clone();
	while(n > 0) {
		mvwordforward(c, capital);
		n--;
	}
	while(n < 0) {
		mvwordbackward(c, capital);
		n++;
	}
	checkcursor(c);
	return c;
}

mvwordforward(c: ref Cursor, cap: int)
{
	x := c.char();
	if(cap)
		stop := whitespace;
	else
		stop = whitespaceinterpunction;

	if(!cap && str->in(x, interpunction))
		while(x >= 0 && str->in(x, interpunction))
			x = c.next();
	while(x >= 0 && !str->in(x, stop))
		x = c.next();
	while(x >= 0 && str->in(x, whitespace))
		x = c.next();
}

mvwordbackward(c: ref Cursor, cap: int)
{
	c.prev();
	x: int;
	x = c.char();
	while(x >= 0 && str->in(x, whitespace))
		x = c.prev();
	if(cap) {
		# read back until whitespace
		c.prev();
		while((x = c.charprev()) >= 0 && !str->in(x, whitespace))
			c.prev();
	} else {
		# if interpunction, read to start of it.
		# otherwise read to end of last whitespace/interpunction
		x = c.char();
		if(x < 0) {
			# nothing
		} else if(str->in(x, interpunction)) {
			while((x = c.charprev()) >= 0 && str->in(x, interpunction))
				c.prev();
		} else {
			while((x = c.charprev()) >= 0 && !str->in(x, whitespaceinterpunction))
				c.prev();
		}
	}
}

Cursor.mvwordend(cc: self ref Cursor, cap: int, n: int): ref Cursor
{
	if(n < 0)
		raise "bad mvwordend";
	c := cc.clone();
	while(n-- > 0) {
		c.next();
		x := c.char();
		while(x >= 0 && str->in(x, whitespace))
			x = c.next();
		if(cap || str->in(x, interpunction))
			stop := whitespace;
		else
			stop = whitespaceinterpunction;
		while(x >= 0 && !str->in(x, stop))
			x = c.next();
	}
	checkcursor(c);
	return c;
}

Cursor.mvfirst(c: self ref Cursor): ref Cursor
{
	return c.clone().mvcol(0).mvskip(" \t");
}

Cursor.mvsentence(c: self ref Cursor, prev: int): ref Cursor
{
	c = c.clone();
	Lineend: con ".!?";
	if(prev) {
		# beginning of previous sentence
		y := c.prev();
		while(y >= 0 && (str->in(y, Lineend) || str->in(y, whitespace)))
			y = c.prev();
		c = c.findchar(Lineend, 1);
		if(c != nil)
			c = c.mvskip(Lineend+whitespace);
		else
			c = text.cursor(0);
	} else {
		c = c.mvskip("^"+Lineend);
		c = c.mvskip(Lineend+whitespace);
	}
	return c;
}

Cursor.mvparagraph(c: self ref Cursor, prev: int): ref Cursor
{
	c = c.clone();
	if(prev) {
		x := c.prev();
		while(x == '\n')
			x = c.prev();
		c = c.findstr("\n\n", 1);
		if(c == nil)
			c = text.cursor(0);
		else
			c.next();
	} else {
		x := c.char();
		while(x == '\n')
			x = c.next();
		c = c.findstr("\n\n", 0);
		if(c == nil)
			c = text.end();
		else
			c.next();
	}
	return c;
}

Cursor.mvskip(cc: self ref Cursor, cl: string): ref Cursor
{
	c := cc.clone();
	x := c.char();
	while(x >= 0 && str->in(x, cl))
		x = c.next();
	return c;
}

# return start & end of word under cursor.  (nil, nil) if cursor not under a word.
Cursor.word(c: self ref Cursor): (ref Cursor, ref Cursor)
{
	x := c.char();
	if(x < 0 || str->in(x, whitespaceinterpunction))
		return (nil, nil);
	a := c.clone();
	b := c.clone();
	while((x = a.charprev()) > 0 && !str->in(x, whitespaceinterpunction))
		a.prev();
	x = b.char();
	while(x > 0 && !str->in(x, whitespaceinterpunction))
		x = b.next();
	return (a, b);
}

# return start & end of path and optionally pattern under cursor.
# (nil, nil) if no path is under the cursor, or no path could be found with `search' set.
Cursor.pathpattern(c: self ref Cursor, search: int): (ref Cursor, ref Cursor)
{
	# read a file:address pattern (not whole line!), also look backwards.
	# this could be done with a vim-like text object "motion" some day.
	Break: con " \t\n!\"'(),;<>?[]{}";
	c = c.clone();
	if(search)
		c = c.mvskip(" \t\n");
	else if(str->in(c.char(), whitespace))
		return (nil, nil);
	ce := c.clone();
	for(;;) {
		x := c.charprev();
		if(x < 0 || has(x, Break))
			break;
		c.prev();
	}
	ncolon := 0;
	x := ce.char();
	for(;;) {
		if(x < 0 || has(x, Break))
			break;
		if(x == ':' && ++ncolon >= 2)
			break;
		x = ce.next();
	}
	return (c, ce);
}

# move cursor forward (or backward if rev!=0) until cursor is at char from cl
# return nil if no such cursor exists.
Cursor.findchar(c: self ref Cursor, cl: string, rev: int): ref Cursor
{
	c = c.clone();
	x := c.char();
	for(;;) {
		if(x < 0)
			return nil;
		if(str->in(x, cl))
			return c;
		if(rev)
			x = c.prev();
		else
			x = c.next();
	}
}

Cursor.findstr(c: self ref Cursor, s: string, rev: int): ref Cursor
{
	if(s == nil)
		return c;
	c = c.clone();
	x := c.char();
	while(x > 0) {
		if(s[0] == x && str->prefix(s, c.b.s[c.o:])) {
			say('c', sprint("findstr, have match, c %s", c.text()));
			return c;
		}
		x = c.walk(rev);
	}
	return nil;
}

Cursor.findlinechar(c: self ref Cursor, x: int, rev: int): ref Cursor
{
	c = c.clone();
	y: int;
	if(rev) {
		if(c.pos.c == 0)
			return nil;
		y = c.prev();
		do {
			if(y == x)
				return c;
			y = c.prev();
		} while(c.pos.c >= 0);
	} else {
		if(c.char() == '\n')
			return nil;
		do {
			y = c.next();
			if(y == x)
				return c;
		} while(y >= 0 && y != '\n');
	}
	return nil;
}

Cursor.text(c: self ref Cursor): string
{
	return sprint("Cursor(len b.s=%d, o=%d, pos=%s)", len c.b.s, c.o, c.pos.text());
}


Buf.new(): ref Buf
{
	return ref Buf ("", 1);
}

Buf.get(b: self ref Buf, f, t: ref Cursor): string
{
	return b.s[f.o:t.o];
}

Buf.del(b: self ref Buf, f, t: ref Cursor): string
{
	r := b.s[f.o:t.o];
	b.s = b.s[:f.o]+b.s[t.o:];
	for(i := 0; i < len r; i++)
		if(r[i] == '\n')
			--b.nlines;
	return r;
}

Buf.ins(b: self ref Buf, cc: ref Cursor, s: string): ref Cursor
{
	c := cc.clone();
	for(i := 0; i < len s; i++) {
		x := s[i];
		if(x == '\n')
			++b.nlines;
		if(len b.s == c.o) {
			b.s[c.o] = x;
		} else {
			ns := c.b.s[:c.o];
			ns[len ns] = x;
			ns += c.b.s[c.o:];
			b.s = ns;
		}
		c.next();
	}
	checkcursor(c);
	return c;
}

Buf.set(b: self ref Buf, s: string)
{
	text.s = s;
	b.nlines = 1;
	for(i := 0; i < len s; i++)
		if(s[i] == '\n')
			++b.nlines;
}

Buf.cursor(b: self ref Buf, o: int): ref Cursor
{
	if(o < 0)
		o = 0;
	if(o > len b.s)
		o = len b.s;
	c := ref Cursor (b, 0, Pos (1, 0));
	for(i := 0; i < o; i++) {
		++c.pos.c;
		if(b.s[i] == '\n') {
			c.pos.c = 0;
			++c.pos.l;
		}
	}
	c.o = i;
	return c;
}

Buf.pos(b: self ref Buf, pos: Pos): ref Cursor
{
	if(pos.l < 1 || pos.c < 0)
		raise "bad pos";
	c := ref Cursor (b, 0, Pos (1, 0));

	x := c.char();
	for(;;) {
		if(x < 0 || c.pos.l == pos.l && (x == '\n' || c.pos.c == pos.c))
			break;
		x = c.next();
	}

	say('c', sprint("Buf.pos %s -> %s", pos.text(), c.pos.text()));
	return c;
}

Buf.lines(b: self ref Buf): int
{
	return b.nlines;
}

Buf.chars(b: self ref Buf): int
{
	return len b.s;
}

Buf.end(b: self ref Buf): ref Cursor
{
	return b.cursor(max(0, b.chars()));
}

Buf.str(b: self ref Buf): string
{
	return b.s;
}

# if cs/ce is nil, write whole buf
bufwritefd(b: ref Buf, cs, ce: ref Cursor, fd: ref Sys->FD): string
{
	if(cs == nil)
		buf := array of byte b.s;
	else
		buf = array of byte text.get(cs, ce);
	if(sys->write(fd, buf, len buf) != len buf)
		return "write failed: %r";
	return nil;
}