~sircmpwn/hare-vt

61657cb33ae5c2e13669ed12d42652295af530db — Drew DeVault 1 year, 1 month ago c57b829
Move chpen into xterm driver
7 files changed, 164 insertions(+), 171 deletions(-)

M example.ha
D vt/diff.ha
A vt/draw.ha
M vt/driver.ha
M vt/pen.ha
M vt/style.ha
M vt/xterm.ha
M example.ha => example.ha +13 -12
@@ 23,10 23,12 @@ export fn main() void = {
};

fn run() (void | vt::error) = {
	const term = vt::open();
	defer vt::close(term);

	const p1 = vt::newpen(void, color::RED, style::ULINE);
	const p2 = vt::newpen(color::YELLOW, color::RED);
	vt::fprint(os::stdout, "oh! ",
		tag(p1, "hello, ", tag(p2, "vt", 100, "!")), "\r\n")?;
	vt::print(term, "oh! ", tag(p1, "hello, ", tag(p2, "vt", 100, "!")), "\r\n")?;

	let in = vt::stdinreader()?;
	for (true) {


@@ 44,8 46,7 @@ fn run() (void | vt::error) = {
		};
		for (let i = 0u8; i < 4; i += 1) {
			if (ev.mods & 1 << i != 0) {
				vt::fprint(os::stdout,
					tag(p1, strmod((1<<i): modflag)), ' ')?;
				vt::print(term, tag(p1, strmod((1<<i): modflag)), ' ')?;
			};
		};



@@ 53,22 54,22 @@ fn run() (void | vt::error) = {
		case let r: rune =>
			switch (r) {
			case '\x08', '\x7f' =>
				fmt::println("BACKSPACE\r")?;
				vt::print(term, "BACKSPACE\r\n")?;
			case '\x09' =>
				fmt::println("TAB\r")?;
				vt::print(term, "TAB\r\n")?;
			case '\x0d' =>
				fmt::println("ENTER\r")?;
				vt::print(term, "ENTER\r\n")?;
			case '\x1b' =>
				fmt::println("ESC\r")?;
				vt::print(term, "ESC\r\n")?;
			case ' ' =>
				fmt::println("SPACE\r")?;
				vt::print(term, "SPACE\r\n")?;
			case =>
				fmt::println(r, '\r')?;
				vt::print(term, r, "\r\n")?;
			};
		case let k: specialkey =>
			fmt::printfln("{}\r", strkey(k))?;
			vt::print(term, strkey(k), "\r\n")?;
		case let f: functionkey =>
			fmt::printfln("F{}\r", f: u8)?;
			vt::print(term, "F", f: u8, "\r\n")?;
		};
	};
};

D vt/diff.ha => vt/diff.ha +0 -85
@@ 1,85 0,0 @@
// TODO: move me to xterm driver private
use fmt;
use io;

// write the minimal escape sequence required to go from one pen to another
fn diff(to: io::handle, p: *pen, q: *pen) (size | io::error) = {
	if (peneq(p, q)) return 0z;
	if (peneq(q, &defaultpen)) {
		return fmt::fprint(to, "\x1b[0m");
	};
	let w = 0z;
	let first = true;
	w += fmt::fprint(to, "\x1b[")?;
	if (!coloreq(p.fg, q.fg)) {
		w += fgstr(to, q.fg)?;
		first = false;
	};
	if (!coloreq(p.bg, q.bg)) {
		if (!first) w += fmt::fprint(to, ";")?;
		w += bgstr(to, q.bg)?;
		first = false;
	};
	w += stylediff(to, p.style, q.style, first)?;
	w += fmt::fprint(to, "m")?;
	return w;
};

fn stylediff(
	to: io::handle,
	a: style,
	b: style,
	first: bool,
) (size | io::error) = {
	let w = 0z;
	for (let i = 0u8; i < len(stylemap); i += 1) {
		if (a & (1 << i) != b & (1 << i)) {
			if (!first) w += fmt::fprint(to, ";")?;
			const e = stylemap[i];
			w += fmt::fprint(to, if (b & (1 << i) != 0) e.0 else e.1)?;
			first = false;
		};
	};
	return w;
};

fn fgstr(to: io::handle, c: anycolor) (size | io::error) = {
	match (c) {
	case void =>
		return fmt::fprint(to, "39");
	case let n: u8 =>
		return fmt::fprintf(to, "38;5;{}", n);
	case let t: rgb8 =>
		return fmt::fprintf(to, "38;2;{};{};{}", t.0, t.1, t.2);
	};
};

fn bgstr(to: io::handle, c: anycolor) (size | io::error) = {
	match (c) {
	case void =>
		return fmt::fprint(to, "49");
	case let n: u8 =>
		return fmt::fprintf(to, "48;5;{}", n);
	case let t: rgb8 =>
		return fmt::fprintf(to, "48;2;{};{};{}", t.0, t.1, t.2);
	};
};

fn peneq(a: *pen, b: *pen) bool = {
	return coloreq(a.fg, b.fg)
		&& coloreq(a.bg, b.bg)
		&& a.style == b.style;
};

fn coloreq(a: anycolor, b: anycolor) bool = {
	if (a is void && b is void) {
		return true;
	} else if (a is u8 && b is u8) {
		return a as u8 == b as u8;
	} else if (a is rgb8 && b is rgb8) {
		const (a, b) = (a as rgb8, b as rgb8);
		return a.0 == b.0 && a.1 == b.1 && a.2 == b.2;
	} else {
		return false;
	};
};

A vt/draw.ha => vt/draw.ha +38 -0
@@ 0,0 1,38 @@
// TODO: Broader set of print/printf/println/printfln equivalents
use fmt;
use io;

// Styled text
export type styled = struct {
	pen: pen,
	args: [](fmt::formattable | styled),
};

// Prepares a styled value with a given pen, for use with [[fprint]].
export fn tag(
	pen: pen,
	args: (fmt::formattable | styled)...,
) styled = styled {
	pen = pen,
	args = args,
};

// Writes styled text to a terminal, restoring the previous pen when complete.
//
// Note that this function does not insert spaces between arguments.
export fn print(
	term: *term,
	args: (fmt::formattable | styled)...
) (void | error) = {
	for (let i = 0z; i < len(args); i += 1) {
		match (args[i]) {
		case let f: fmt::formattable =>
			fmt::fprint(term, f)?;
		case let t: styled =>
			term.driver.chpen(term, &t.pen)?;
			print(term, t.args...)?;
		};
	};
	// TODO: Restore previous pen
	term.driver.chpen(term, &defaultpen)?;
};

M vt/driver.ha => vt/driver.ha +3 -5
@@ 3,11 3,8 @@ use io;
use os;

export type driver = struct {
	// Sets the current pen for drawing.
	setpen: *fn(term: *term, pen: *pen) (void | error),

	// Writes text to the driver using the current pen.
	write: *fn(term: *term, data: []u8) (size | error),
	// Changes the current pen for drawing.
	chpen: *fn(term: *term, pen: *pen) (void | error),

	// Polls for the next input event from this driver.
	pollev: *fn(term: *term, block: bool) (void | event | io::EOF | error),


@@ 17,6 14,7 @@ export type driver = struct {
};

export type term = struct {
	stream: io::stream,
	driver: const *driver,
};


M vt/pen.ha => vt/pen.ha +5 -50
@@ 10,12 10,6 @@ export type pen = struct {
	style: style,
};

// Some styled text, pending formatting
export type tagged = struct {
	p: pen,
	args: [](fmt::formattable | tagged),
};

// the default pen
export const defaultpen = pen {
	fg = void,


@@ 36,48 30,9 @@ export fn newpen(bg: anycolor, fg: anycolor, styles: style...) pen = {
	return p;
};

// TODO: Move everything from here down to draw.ha and take [[term]] input

// Tags a value tagged with a given pen, for use with [[fprint]].
export fn tag(
	p: pen,
	args: (fmt::formattable | tagged)...,
) tagged = tagged {
	p = p,
	args = args,
};

// Writes styled text to an [[io::handle]], allowing nesting, and optimizing to
// remove unneeded escape codes.
//
// Note that this function does not insert spaces between arguments.
export fn fprint(
	to: io::handle,
	args: (fmt::formattable | tagged)...,
) (size | io::error) = {
	let w = 0z;
	let current = defaultpen;
	w += fprint_(&current, &defaultpen, to, args...)?;
	if (!peneq(&current, &defaultpen)) {
		w += diff(to, &current, &defaultpen)?;
	};
	return w;
};

fn fprint_(
	current: *pen,
	p: *const pen,
	to: io::handle,
	args: (fmt::formattable | tagged)...,
) (size | io::error) = {
	let w = 0z;
	for (let i = 0z; i < len(args); i += 1) match (args[i]) {
	case let f: fmt::formattable =>
		w += diff(to, current, p)?;
		*current = *p;
		w += fmt::fprint(to, f)?;
	case let t: tagged =>
		w += fprint_(current, &t.p, to, t.args...)?;
	};
	return w;
// Returns true if two pens are equivalent.
export fn peneq(a: *pen, b: *pen) bool = {
	return coloreq(a.fg, b.fg)
		&& coloreq(a.bg, b.bg)
		&& a.style == b.style;
};

M vt/style.ha => vt/style.ha +14 -10
@@ 9,16 9,6 @@ export type style = enum u8 {
	STRIKE = 1 << 5,
};

// Ordered pairs (on, off)
const stylemap: [](str, str) = [
	("1", "21"), // bold
	("2", "22"), // dim
	("4", "24"), // underline
	("5", "25"), // blink
	("7", "27"), // invert
	("9", "29"), // strike
];

// a color. not that void represents the reset color, not unspecified color.
export type anycolor = (void | u8 | rgb8);



@@ 97,3 87,17 @@ const grays: [25]u8 = [

// 24-bit color separated into R, G, B channels.
export type rgb8 = (u8, u8, u8);

// Returns true if two colors are equivalent.
export fn coloreq(a: anycolor, b: anycolor) bool = {
	if (a is void && b is void) {
		return true;
	} else if (a is u8 && b is u8) {
		return a as u8 == b as u8;
	} else if (a is rgb8 && b is rgb8) {
		const (a, b) = (a as rgb8, b as rgb8);
		return a.0 == b.0 && a.1 == b.1 && a.2 == b.2;
	} else {
		return false;
	};
};

M vt/xterm.ha => vt/xterm.ha +91 -9
@@ 1,3 1,4 @@
use fmt;
use io;
use os;
use unix::tty;


@@ 14,12 15,17 @@ type xterm = struct {
};

const xterm_driver = driver {
	setpen = &xterm_setpen,
	write = &xterm_write,
	chpen = &xterm_chpen,
	pollev = &xterm_pollev,
	destroy = &xterm_destroy,
};

const xterm_stream = io::vtable {
	reader = &xterm_read,
	writer = &xterm_write,
	...
};

fn xterm_init(in: io::file, out: io::file) *term = {
	let termios = tty::termios { ... };
	if (tty::isatty(in)) {


@@ 28,6 34,7 @@ fn xterm_init(in: io::file, out: io::file) *term = {
		tty::noecho(&termios)!;
	};
	return alloc(xterm {
		stream = &xterm_stream,
		driver = &xterm_driver,
		in = in,
		out = out,


@@ 43,20 50,95 @@ fn xterm_destroy(term: *term) void = {
	free(term);
};

fn xterm_write(term: *term, data: []u8) (size | error) = {
	return io::write((term: *xterm).out, data)?;
fn xterm_read(term: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
	abort(); // TODO
};

fn xterm_write(term: *io::stream, buf: const []u8) (size | io::error) = {
	return io::write((term: *xterm).out, buf);
};

fn xterm_setpen(term: *term, pen: *pen) (void | error) = {
fn xterm_chpen(term: *term, pen: *pen) (void | error) = {
	let term = term: *xterm;
	if (!peneq(&term.pen, pen)) {
		// TODO: Move to xterm driver
		diff(term.out, pen, &term.pen)?;
	};
	const old = term.pen;
	term.pen = *pen;

	if (peneq(&old, pen)) {
		return;
	};
	if (peneq(pen, &defaultpen)) {
		fmt::fprint(term.out, "\x1b[0m")?;
		return;
	};

	let first = true;
	fmt::fprint(term.out, "\x1b[")?;
	if (!coloreq(pen.fg, old.fg)) {
		xterm_fgstr(term.out, pen.fg)?;
		first = false;
	};
	if (!coloreq(pen.bg, old.bg)) {
		if (!first) {
			fmt::fprint(term.out, ";")?;
		};
		xterm_bgstr(term.out, pen.bg)?;
		first = false;
	};
	xterm_chstyle(term.out, old.style, pen.style, first)?;
	fmt::fprint(term.out, "m")?;
};

fn xterm_pollev(term: *term, block: bool) (void | event | io::EOF | error) = {
	let term = term: *xterm;
	abort(); // TODO
};

fn xterm_fgstr(out: io::file, c: anycolor) (void | io::error) = {
	match (c) {
	case void =>
		fmt::fprint(out, "39")?;
	case let n: u8 =>
		fmt::fprintf(out, "38;5;{}", n)?;
	case let t: rgb8 =>
		fmt::fprintf(out, "38;2;{};{};{}", t.0, t.1, t.2)?;
	};
};

fn xterm_bgstr(out: io::file, c: anycolor) (void | io::error) = {
	match (c) {
	case void =>
		fmt::fprint(out, "49")?;
	case let n: u8 =>
		fmt::fprintf(out, "48;5;{}", n)?;
	case let t: rgb8 =>
		fmt::fprintf(out, "48;2;{};{};{}", t.0, t.1, t.2)?;
	};
};

// Ordered pairs (on, off)
const xterm_stylemap: [](str, str) = [
	("1", "21"), // bold
	("2", "22"), // dim
	("4", "24"), // underline
	("5", "25"), // blink
	("7", "27"), // invert
	("9", "29"), // strike
];

fn xterm_chstyle(
	out: io::file,
	a: style,
	b: style,
	first: bool,
) (void | io::error) = {
	for (let i = 0u8; i < len(xterm_stylemap); i += 1) {
		if (a & (1 << i) != b & (1 << i)) {
			if (!first) {
				fmt::fprint(out, ";")?;
			};
			const e = xterm_stylemap[i];
			fmt::fprint(out, if (b & (1 << i) != 0) e.0 else e.1)?;
			first = false;
		};
	};
};