summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.ha241
1 files changed, 129 insertions, 112 deletions
diff --git a/main.ha b/main.ha
index 5ad650b..a33db83 100644
--- a/main.ha
+++ b/main.ha
@@ -28,19 +28,26 @@ use strio;
use time;
use unix::poll;
use unix::tty;
+use vt;
+
+type state = struct {
+ subtitles: []subtitle,
+
+ start: time::instant,
+ length: time::duration,
+ elapsed: time::duration,
+ pause_start: (time::instant | void),
+
+ term: *vt::term,
+};
type subtitle = struct {
index: uint,
start: time::duration,
end: time::duration,
- text: []text,
+ text: vt::styled,
};
-type text = (str | setbold | setitalic | setunderline);
-type setbold = bool;
-type setitalic = bool;
-type setunderline = bool;
-
export fn main() void = {
let help: []getopt::help = [
"Play a .srt subtitle file",
@@ -73,74 +80,88 @@ export fn main() void = {
io::close(file)!;
defer free(subtitles);
defer for (let i = 0z; i < len(subtitles); i += 1) {
- free_text(subtitles[i].text);
+ let text = subtitles[i].text;
+ for (let j = 0z; j < len(text.args); j += 1) {
+ free((text.args[j] as vt::styled).args[0] as str);
+ };
+ free(text.args);
};
- fmt::fprint(os::stdout_file, "\x1b[?25l")!; // Hide the cursor
- defer fmt::fprint(os::stdout_file, "\x1b[?25h")!;
- let termios = tty::termios_query(os::stdin_file)!;
- defer tty::termios_restore(&termios);
- tty::makeraw(&termios)!;
-
- let pollfds = [
- poll::pollfd {
- fd = os::stdin_file,
- events = poll::event::POLLIN,
- ...
- },
- ];
+ let state = state {
+ start = time::now(time::clock::REALTIME),
+ term = vt::open(),
+ pause_start = void,
+ subtitles = subtitles,
+ ...
+ };
+ defer vt::close(state.term);
+ vt::disablecur(state.term)!;
- let end: time::duration = 0;
for (let i = 0z; i < len(subtitles); i += 1) {
- if (subtitles[i].end > end) {
- end = subtitles[i].end;
+ if (subtitles[i].end > state.length) {
+ state.length = subtitles[i].end;
};
};
- let start = time::now(time::clock::REALTIME);
- let elapsed: time::duration = 0;
- let pause_start: (time::instant | void) = void;
+ match (run(state)) {
+ case void => void;
+ case let err: vt::error =>
+ vt::close(state.term);
+ fmt::fatal("Error:", vt::strerror(err));
+ };
+};
+
+fn run(state: state) (void | vt::error) = {
for (true) {
- match (pause_start) {
+ match (state.pause_start) {
case void =>
let now = time::now(time::clock::REALTIME);
- elapsed = time::diff(start, now);
+ state.elapsed = time::diff(state.start, now);
case let inst: time::instant =>
- elapsed = time::diff(start, inst);
+ state.elapsed = time::diff(state.start, inst);
};
- if (elapsed > end) break;
-
- fmt::printfln("\x1b[2J\x1b[;H{:02}:{:02}:{:02}{}\r",
- elapsed / time::HOUR,
- elapsed / time::MINUTE % 60,
- elapsed / time::SECOND % 60,
- if (pause_start is time::instant) " (PAUSED)" else "",
- )!;
-
- let timeout = end;
-
- for (let i = 0z; i < len(subtitles); i += 1) {
- let seg = subtitles[i];
- if (seg.start > elapsed && seg.start < timeout) {
- timeout = seg.start;
+ if (state.elapsed > state.length) break;
+
+ vt::clear(state.term)?;
+ let time_text = fmt::asprintf(
+ "{:02}:{:02}:{:02}{}\r\n",
+ state.elapsed / time::HOUR,
+ state.elapsed / time::MINUTE % 60,
+ state.elapsed / time::SECOND % 60,
+ if (state.pause_start is time::instant) " (PAUSED)" else "",
+ );
+ defer free(time_text);
+ vt::print(state.term, time_text)?;
+
+ let timeout = state.length;
+
+ for (let i = 0z; i < len(state.subtitles); i += 1) {
+ let sub = state.subtitles[i];
+ if (sub.start > state.elapsed && sub.start < timeout) {
+ timeout = sub.start;
};
- if (seg.end > elapsed && seg.end < timeout) {
- timeout = seg.end;
+ if (sub.end > state.elapsed && sub.end < timeout) {
+ timeout = sub.end;
};
- if (seg.start <= elapsed && elapsed <= seg.end) {
- print_subtitle(os::stdout, seg.text)!;
+ if (sub.start <= state.elapsed && state.elapsed <= sub.end) {
+ vt::print(state.term, sub.text)?;
};
};
- timeout -= elapsed;
- let next_second = time::SECOND - elapsed % time::SECOND;
+ timeout -= state.elapsed;
+ let next_second = time::SECOND - state.elapsed % time::SECOND;
if (timeout > next_second) {
timeout = next_second;
};
- if (pause_start is time::instant) {
+ if (state.pause_start is time::instant) {
timeout = poll::INDEF;
};
+ let pollfds = [poll::pollfd {
+ fd = os::stdin_file,
+ events = poll::event::POLLIN,
+ ...
+ }];
match (poll::poll(pollfds, timeout)) {
case uint => void;
case let err: poll::error =>
@@ -167,26 +188,26 @@ export fn main() void = {
case 'q' =>
quit = true;
case 'j' =>
- start = time::add(start, time::SECOND * 10);
- match (pause_start) {
+ state.start = time::add(state.start, time::SECOND * 10);
+ match (state.pause_start) {
case void =>
- if (time::diff(start, now) < 0) {
- start = now;
+ if (time::diff(state.start, now) < 0) {
+ state.start = now;
};
case let inst: time::instant =>
- if (time::diff(start, inst) < 0) {
- start = inst;
+ if (time::diff(state.start, inst) < 0) {
+ state.start = inst;
};
};
case 'l' =>
- start = time::add(start, -time::SECOND * 10);
+ state.start = time::add(state.start, -time::SECOND * 10);
case 'k' =>
- match (pause_start) {
+ match (state.pause_start) {
case void =>
- pause_start = now;
+ state.pause_start = now;
case let inst: time::instant =>
- pause_start = void;
- start = time::add(start, time::diff(inst, now));
+ state.pause_start = void;
+ state.start = time::add(state.start, time::diff(inst, now));
};
case => void;
@@ -196,7 +217,7 @@ export fn main() void = {
};
};
-type state = enum {
+type parser_state = enum {
INDEX,
TIMECODE,
TEXT,
@@ -206,11 +227,17 @@ def XML_PROLOG: str = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n";
fn parse_srt(file: io::handle) []subtitle = {
let subtitles: []subtitle = [];
- let current = subtitle { ... };
+ let current = subtitle {
+ text = vt::styled {
+ pen = vt::defaultpen,
+ ...
+ },
+ ...
+ };
let content = strio::dynamic();
defer io::close(&content)!;
strio::concat(&content, XML_PROLOG)!;
- let state = state::INDEX;
+ let state = parser_state::INDEX;
for (let nr = 0; true; nr += 1) {
let line = match (bufio::scanline(file)!) {
@@ -227,15 +254,15 @@ fn parse_srt(file: io::handle) []subtitle = {
defer free(line);
switch (state) {
- case state::INDEX =>
+ case parser_state::INDEX =>
match (strconv::stou(line)) {
case let index: uint =>
current.index = index;
case =>
fmt::fatalf("Error on line {}: expected uint, found '{}'\n", nr, line);
};
- state = state::TIMECODE;
- case state::TIMECODE =>
+ state = parser_state::TIMECODE;
+ case parser_state::TIMECODE =>
match (parse_timecode(line)) {
case let times: (time::duration, time::duration) =>
current.start = times.0;
@@ -243,8 +270,8 @@ fn parse_srt(file: io::handle) []subtitle = {
case =>
fmt::fatalf("Error on line {}: invalid timecode syntax\n", nr);
};
- state = state::TEXT;
- case state::TEXT =>
+ state = parser_state::TEXT;
+ case parser_state::TEXT =>
if (len(line) > 0) {
strio::concat(&content, line, "\n")!;
continue;
@@ -254,21 +281,31 @@ fn parse_srt(file: io::handle) []subtitle = {
let content_str = strio::string(&content);
let buf = bufio::fixed(strings::toutf8(content_str), io::mode::READ);
current.text = match (parse_text(&buf)) {
- case let t: []text =>
- yield t;
+ case let text: vt::styled =>
+ yield text;
case =>
let content = strings::trimprefix(content_str, XML_PROLOG);
let content = strings::trimsuffix(content, "</root>");
- yield alloc([strings::replace(content, "\n", "\r\n")]);
+ let text = vt::styled {
+ pen = vt::defaultpen,
+ args = [strings::replace(content, "\n", "\r\n")],
+ };
+ yield text;
};
append(subtitles, current);
- current = subtitle { ... };
+ current = subtitle {
+ text = vt::styled {
+ pen = vt::defaultpen,
+ ...
+ },
+ ...
+ };
strio::reset(&content);
strio::concat(&content, XML_PROLOG)!;
- state = state::INDEX;
+ state = parser_state::INDEX;
};
};
@@ -296,11 +333,15 @@ fn parse_time(time: str) (time::duration | strconv::invalid | strconv::overflow)
return dur;
};
-fn parse_text(in: io::handle) ([]text | io::error | xml::error) = {
+fn parse_text(in: io::handle) (vt::styled | io::error | xml::error) = {
let parser = xml::parse(in)?;
defer xml::parser_free(parser);
- let text: []text = [];
+ let text = vt::styled {
+ pen = vt::defaultpen,
+ ...
+ };
+ let pen = vt::defaultpen;
let bold = 0;
let italic = 0;
let underline = 0;
@@ -317,17 +358,17 @@ fn parse_text(in: io::handle) ([]text | io::error | xml::error) = {
switch (start) {
case "b" =>
if (bold == 0) {
- append(text, true: setbold);
+ pen.style |= vt::style::BOLD;
};
bold += 1;
case "i" =>
if (italic == 0) {
- append(text, true: setitalic);
+ pen.style |= vt::style::ITALIC;
};
italic += 1;
case "u" =>
if (underline == 0) {
- append(text, true: setunderline);
+ pen.style |= vt::style::ULINE;
};
underline += 1;
case => void;
@@ -336,53 +377,29 @@ fn parse_text(in: io::handle) ([]text | io::error | xml::error) = {
switch (end) {
case "b" =>
if (bold == 1) {
- append(text, false: setbold);
+ pen.style &= ~vt::style::BOLD;
};
bold -= 1;
case "i" =>
if (italic == 1) {
- append(text, false: setitalic);
+ pen.style &= ~vt::style::ITALIC;
};
italic -= 1;
case "u" =>
if (underline == 1) {
- append(text, false: setunderline);
+ pen.style &= ~vt::style::ULINE;
};
underline -= 1;
case => void;
};
case xml::attribute => void;
case let t: xml::text =>
- // Necessary because of raw mode
- append(text, strings::replace(t, "\n", "\r\n"));
+ let styled = vt::styled {
+ pen = pen,
+ args = alloc([strings::replace(t, "\n", "\r\n")]),
+ };
+ append(text.args, styled);
};
};
return text;
};
-
-fn print_subtitle(out: io::handle, text: []text) (void | io::error) = {
- for (let i = 0z; i < len(text); i += 1) {
- let output = match (text[i]) {
- case let s: str =>
- yield s;
- case let bold: setbold =>
- yield if (bold) "\x1b[1m" else "\x1b[22m";
- case let italic: setitalic =>
- yield if (italic) "\x1b[3m" else "\x1b[23m";
- case let underline: setunderline =>
- yield if (underline) "\x1b[4m" else "\x1b[24m";
- };
- io::writeall(out, strings::toutf8(output))?;
- };
-};
-
-fn free_text(text: []text) void = {
- for (let i = 0z; i < len(text); i += 1) {
- match (text[i]) {
- case let s: str =>
- free(s);
- case => void;
- };
- };
- free(text);
-};