From a4bca446049fd488af29dd5ec26f953845e8e4fe Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 6 Mar 2021 18:45:50 -0500 Subject: split into sync and async implementations --- Cargo.toml | 10 ++++++ examples/async.rs | 29 ++++++++++++++++++ examples/basic.rs | 4 ++- src/async.rs | 67 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 91 +++++++++++++++++++------------------------------------ src/sync.rs | 65 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 61 deletions(-) create mode 100644 examples/async.rs create mode 100644 src/async.rs create mode 100644 src/sync.rs diff --git a/Cargo.toml b/Cargo.toml index 1007f0d..08b72cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,13 @@ edition = "2018" itoa = "0.4" terminal_size = "0.1" vt100 = "0.9" + +blocking = { version = "1.0", optional = true } +futures-lite = { version = "1.11", optional = true } + +[features] +default = [] +async = ["blocking", "futures-lite"] + +[dev-dependencies] +smol = "1.2" diff --git a/examples/async.rs b/examples/async.rs new file mode 100644 index 0000000..e8e4282 --- /dev/null +++ b/examples/async.rs @@ -0,0 +1,29 @@ +use textmode::Textmode as _; + +async fn run(tm: &mut textmode::r#async::Textmode) -> std::io::Result<()> { + tm.move_to(5, 5); + tm.write_str("foo"); + smol::Timer::after(std::time::Duration::from_secs(2)).await; + tm.refresh().await?; + smol::Timer::after(std::time::Duration::from_secs(2)).await; + + tm.move_to(8, 8); + tm.set_fgcolor(textmode::color::GREEN); + tm.write_str("bar"); + tm.move_to(11, 11); + tm.set_fgcolor(vt100::Color::Default); + tm.write_str("baz"); + smol::Timer::after(std::time::Duration::from_secs(2)).await; + tm.refresh().await?; + smol::Timer::after(std::time::Duration::from_secs(2)).await; + Ok(()) +} + +fn main() { + smol::block_on(async { + let mut tm = textmode::r#async::Textmode::new().await.unwrap(); + let e = run(&mut tm).await; + tm.cleanup().await.unwrap(); + e.unwrap(); + }); +} diff --git a/examples/basic.rs b/examples/basic.rs index 1026834..150c9b0 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,5 +1,7 @@ +use textmode::Textmode as _; + fn main() { - let mut tm = textmode::Textmode::new().unwrap(); + let mut tm = textmode::sync::Textmode::new().unwrap(); tm.move_to(5, 5); tm.write_str("foo"); diff --git a/src/async.rs b/src/async.rs new file mode 100644 index 0000000..a384da8 --- /dev/null +++ b/src/async.rs @@ -0,0 +1,67 @@ +use futures_lite::io::AsyncWriteExt as _; + +use super::private::TextmodeImpl as _; + +pub struct Textmode { + cur: vt100::Parser, + next: vt100::Parser, +} + +impl super::private::TextmodeImpl for Textmode { + fn cur(&self) -> &vt100::Parser { + &self.cur + } + + fn cur_mut(&mut self) -> &mut vt100::Parser { + &mut self.cur + } + + fn next(&self) -> &vt100::Parser { + &self.next + } + + fn next_mut(&mut self) -> &mut vt100::Parser { + &mut self.next + } +} + +impl super::Textmode for Textmode {} + +impl Textmode { + pub async fn new() -> std::io::Result { + let (rows, cols) = match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (h, w) + } + _ => (24, 80), + }; + let cur = vt100::Parser::new(rows, cols, 0); + let next = vt100::Parser::new(rows, cols, 0); + + let self_ = Self { cur, next }; + self_ + .write_stdout(b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h") + .await?; + Ok(self_) + } + + // TODO: without async drop or async closures, i'm not sure how to do + // better than this + pub async fn cleanup(&mut self) -> std::io::Result<()> { + self.write_stdout(b"\x1b[?47l\x1b8\x1b[?25h").await + } + + pub async fn refresh(&mut self) -> std::io::Result<()> { + let diff = self.next().screen().contents_diff(self.cur().screen()); + self.write_stdout(&diff).await?; + self.cur_mut().process(&diff); + Ok(()) + } + + async fn write_stdout(&self, buf: &[u8]) -> std::io::Result<()> { + let mut stdout = blocking::Unblock::new(std::io::stdout()); + stdout.write_all(buf).await?; + stdout.flush().await?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 39c201b..fcee1e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,53 +1,47 @@ -use std::io::Write as _; - pub mod color; -pub struct Textmode { - cur: vt100::Parser, - next: vt100::Parser, -} +#[cfg(feature = "async")] +pub mod r#async; +pub mod sync; -impl Textmode { - pub fn new() -> std::io::Result { - let (rows, cols) = match terminal_size::terminal_size() { - Some((terminal_size::Width(w), terminal_size::Height(h))) => { - (h, w) - } - _ => (24, 80), - }; - let cur = vt100::Parser::new(rows, cols, 0); - let next = vt100::Parser::new(rows, cols, 0); +mod private { + pub trait TextmodeImpl { + fn cur(&self) -> &vt100::Parser; + fn cur_mut(&mut self) -> &mut vt100::Parser; + fn next(&self) -> &vt100::Parser; + fn next_mut(&mut self) -> &mut vt100::Parser; - let self_ = Self { cur, next }; - self_.write_stdout(b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h")?; - Ok(self_) - } + fn write_u16(&mut self, i: u16) { + // unwrap is fine because vt100::Parser::write can never fail + itoa::write(self.next_mut(), i).unwrap(); + } - pub fn cursor_position(&self) -> (u16, u16) { - self.next.screen().cursor_position() + fn write_u8(&mut self, i: u8) { + // unwrap is fine because vt100::Parser::write can never fail + itoa::write(self.next_mut(), i).unwrap(); + } } +} - pub fn write(&mut self, buf: &[u8]) { - self.next.process(buf); +pub trait Textmode: private::TextmodeImpl { + fn cursor_position(&self) -> (u16, u16) { + self.next().screen().cursor_position() } - pub fn refresh(&mut self) -> std::io::Result<()> { - let diff = self.next.screen().contents_diff(self.cur.screen()); - self.write_stdout(&diff)?; - self.cur.process(&diff); - Ok(()) + fn write(&mut self, buf: &[u8]) { + self.next_mut().process(buf); } - pub fn set_size(&mut self, rows: u16, cols: u16) { - self.cur.set_size(rows, cols); - self.next.set_size(rows, cols); + fn set_size(&mut self, rows: u16, cols: u16) { + self.cur_mut().set_size(rows, cols); + self.next_mut().set_size(rows, cols); } - pub fn write_str(&mut self, text: &str) { + fn write_str(&mut self, text: &str) { self.write(text.as_bytes()); } - pub fn move_to(&mut self, row: u16, col: u16) { + fn move_to(&mut self, row: u16, col: u16) { self.write(b"\x1b["); self.write_u16(row); self.write(b";"); @@ -55,11 +49,11 @@ impl Textmode { self.write(b"H"); } - pub fn clear(&mut self) { + fn clear(&mut self) { self.write(b"\x1b[2J"); } - pub fn set_fgcolor(&mut self, color: vt100::Color) { + fn set_fgcolor(&mut self, color: vt100::Color) { match color { vt100::Color::Default => { self.write(b"\x1b[39m"); @@ -87,7 +81,7 @@ impl Textmode { } } - pub fn set_bgcolor(&mut self, color: vt100::Color) { + fn set_bgcolor(&mut self, color: vt100::Color) { match color { vt100::Color::Default => { self.write(b"\x1b[49m"); @@ -114,27 +108,4 @@ impl Textmode { } } } - - fn write_u16(&mut self, i: u16) { - // unwrap is fine because vt100::Parser::write can never fail - itoa::write(&mut self.next, i).unwrap(); - } - - fn write_u8(&mut self, i: u8) { - // unwrap is fine because vt100::Parser::write can never fail - itoa::write(&mut self.next, i).unwrap(); - } - - fn write_stdout(&self, buf: &[u8]) -> std::io::Result<()> { - let mut stdout = std::io::stdout(); - stdout.write_all(buf)?; - stdout.flush()?; - Ok(()) - } -} - -impl Drop for Textmode { - fn drop(&mut self) { - let _ = self.write_stdout(b"\x1b[?47l\x1b8\x1b[?25h"); - } } diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 0000000..047036d --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,65 @@ +use std::io::Write as _; + +use super::private::TextmodeImpl as _; + +pub struct Textmode { + cur: vt100::Parser, + next: vt100::Parser, +} + +impl super::private::TextmodeImpl for Textmode { + fn cur(&self) -> &vt100::Parser { + &self.cur + } + + fn cur_mut(&mut self) -> &mut vt100::Parser { + &mut self.cur + } + + fn next(&self) -> &vt100::Parser { + &self.next + } + + fn next_mut(&mut self) -> &mut vt100::Parser { + &mut self.next + } +} + +impl super::Textmode for Textmode {} + +impl Textmode { + pub fn new() -> std::io::Result { + let (rows, cols) = match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (h, w) + } + _ => (24, 80), + }; + let cur = vt100::Parser::new(rows, cols, 0); + let next = vt100::Parser::new(rows, cols, 0); + + let self_ = Self { cur, next }; + self_.write_stdout(b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h")?; + Ok(self_) + } + + pub fn refresh(&mut self) -> std::io::Result<()> { + let diff = self.next().screen().contents_diff(self.cur().screen()); + self.write_stdout(&diff)?; + self.cur_mut().process(&diff); + Ok(()) + } + + fn write_stdout(&self, buf: &[u8]) -> std::io::Result<()> { + let mut stdout = std::io::stdout(); + stdout.write_all(buf)?; + stdout.flush()?; + Ok(()) + } +} + +impl Drop for Textmode { + fn drop(&mut self) { + let _ = self.write_stdout(b"\x1b[?47l\x1b8\x1b[?25h"); + } +} -- cgit v1.2.3-54-g00ecf