aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-11-06 14:32:17 -0500
committerJesse Luehrs <doy@tozt.net>2019-11-06 14:32:17 -0500
commitcd3a22971cecd24960ea73d42d20c9ce00cdf2d6 (patch)
tree1436ebfd913922daabc7f71b1fef80bd2f9fe3f1
parent726fc4418c204ec6339f2453f7e8603435d9e729 (diff)
downloadteleterm-cd3a22971cecd24960ea73d42d20c9ce00cdf2d6.tar.gz
teleterm-cd3a22971cecd24960ea73d42d20c9ce00cdf2d6.zip
implement seeking in playing ttyrecs
-rw-r--r--Cargo.lock109
-rw-r--r--Cargo.toml1
-rw-r--r--src/cmd/play.rs142
3 files changed, 223 insertions, 29 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fb4de01..ab699d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -183,7 +183,7 @@ dependencies = [
"ansi_term",
"atty",
"bitflags",
- "strsim",
+ "strsim 0.8.0",
"term_size",
"textwrap",
"unicode-width",
@@ -402,6 +402,41 @@ dependencies = [
]
[[package]]
+name = "darling"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2 1.0.6",
+ "quote 1.0.2",
+ "strsim 0.9.2",
+ "syn 1.0.5",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
+dependencies = [
+ "darling_core",
+ "quote 1.0.2",
+ "syn 1.0.5",
+]
+
+[[package]]
name = "digest"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -460,6 +495,28 @@ dependencies = [
]
[[package]]
+name = "enumset"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b811aef4ff1cc938f13bbec348f0ecbfc2bb565b7ab90161c9f0b2805edc8a"
+dependencies = [
+ "enumset_derive",
+ "num-traits",
+]
+
+[[package]]
+name = "enumset_derive"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b184c2d0714bbeeb6440481a19c78530aa210654d99529f13d2f860a1b447598"
+dependencies = [
+ "darling",
+ "proc-macro2 1.0.6",
+ "quote 1.0.2",
+ "syn 1.0.5",
+]
+
+[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -726,6 +783,12 @@ dependencies = [
]
[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
name = "idna"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -950,6 +1013,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db1b4163932b207be6e3a06412aed4d84cca40dc087419f231b3a38cba2ca8e9"
[[package]]
+name = "num-traits"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "num_cpus"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1588,6 +1660,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
+name = "strsim"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6"
+
+[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1655,6 +1733,7 @@ dependencies = [
"url 2.1.0",
"users",
"uuid 0.8.1",
+ "vt100",
]
[[package]]
@@ -2119,6 +2198,12 @@ dependencies = [
]
[[package]]
+name = "utf8parse"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
+
+[[package]]
name = "uuid"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2155,6 +2240,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
+name = "vt100"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95036fec2baf2d86abb01a81f6eb1ad3050d6a53026af54f5c386a1147f3125c"
+dependencies = [
+ "enumset",
+ "log",
+ "unicode-normalization",
+ "unicode-width",
+ "vte",
+]
+
+[[package]]
+name = "vte"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
name = "want"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index bb1b081..75d4ea5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -42,6 +42,7 @@ twoway = "0.2"
url = "2"
users = "0.9"
uuid = { version = "0.8", features = ["v4"] }
+vt100 = "0.3"
[[bin]]
name = "tt"
diff --git a/src/cmd/play.rs b/src/cmd/play.rs
index acc0673..a3986ea 100644
--- a/src/cmd/play.rs
+++ b/src/cmd/play.rs
@@ -55,7 +55,8 @@ pub fn config(
struct Frame {
dur: std::time::Duration,
- data: Vec<u8>,
+ full: Vec<u8>,
+ diff: Vec<u8>,
}
impl Frame {
@@ -96,6 +97,7 @@ struct Player {
timer: Option<tokio::timer::Delay>,
base_time: std::time::Instant,
played_amount: std::time::Duration,
+ paused: Option<std::time::Instant>,
}
impl Player {
@@ -111,9 +113,14 @@ impl Player {
timer: None,
base_time: std::time::Instant::now(),
played_amount: std::time::Duration::default(),
+ paused: None,
}
}
+ fn current_frame(&self) -> Option<&Frame> {
+ self.ttyrec.frame(self.idx)
+ }
+
fn base_time_incr(&mut self, incr: std::time::Duration) {
self.base_time += incr;
self.set_timer();
@@ -141,6 +148,46 @@ impl Player {
self.set_timer();
}
+ fn back(&mut self) {
+ self.idx = self.idx.saturating_sub(1);
+ self.recalculate_times();
+ self.set_timer();
+ }
+
+ fn forward(&mut self) {
+ self.idx = self.idx.saturating_add(1);
+ self.recalculate_times();
+ self.set_timer();
+ }
+
+ fn toggle_pause(&mut self) {
+ let now = std::time::Instant::now();
+ if let Some(time) = self.paused.take() {
+ self.base_time_incr(now - time);
+ } else {
+ self.paused = Some(now);
+ }
+ }
+
+ fn paused(&self) -> bool {
+ self.paused.is_some()
+ }
+
+ fn recalculate_times(&mut self) {
+ let now = std::time::Instant::now();
+ self.played_amount = self
+ .ttyrec
+ .frames
+ .iter()
+ .map(|f| f.dur)
+ .take(self.idx)
+ .sum();
+ self.base_time = now - self.played_amount;
+ if let Some(paused) = &mut self.paused {
+ *paused = now;
+ }
+ }
+
fn set_timer(&mut self) {
if let Some(frame) = self.ttyrec.frame(self.idx) {
self.timer = Some(tokio::timer::Delay::new(
@@ -169,7 +216,7 @@ impl Player {
};
futures::try_ready!(timer.poll().context(crate::error::Sleep));
- let ret = frame.data.clone();
+ let ret = frame.diff.clone();
self.idx += 1;
self.played_amount +=
@@ -191,6 +238,7 @@ enum FileState {
},
Open {
reader: ttyrec::Reader<tokio::fs::File>,
+ parser: vt100::Parser,
},
Eof,
}
@@ -199,9 +247,10 @@ struct PlaySession {
file: FileState,
player: Player,
raw_screen: Option<crossterm::RawScreen>,
+ alternate_screen: Option<crossterm::AlternateScreen>,
key_reader: crate::key_reader::KeyReader,
last_frame_time: std::time::Duration,
- paused: Option<std::time::Instant>,
+ last_frame_screen: Option<vt100::Screen>,
}
impl PlaySession {
@@ -216,9 +265,10 @@ impl PlaySession {
},
player: Player::new(playback_ratio, max_frame_length),
raw_screen: None,
+ alternate_screen: None,
key_reader: crate::key_reader::KeyReader::new(),
last_frame_time: std::time::Duration::default(),
- paused: None,
+ last_frame_screen: None,
}
}
@@ -230,12 +280,7 @@ impl PlaySession {
crossterm::InputEvent::Keyboard(crossterm::KeyEvent::Char(
' ',
)) => {
- if let Some(time) = self.paused.take() {
- self.player
- .base_time_incr(std::time::Instant::now() - time);
- } else {
- self.paused = Some(std::time::Instant::now());
- }
+ self.player.toggle_pause();
}
crossterm::InputEvent::Keyboard(crossterm::KeyEvent::Char(
'+',
@@ -252,10 +297,41 @@ impl PlaySession {
)) => {
self.player.playback_ratio_reset();
}
+ crossterm::InputEvent::Keyboard(crossterm::KeyEvent::Char(
+ '<',
+ )) => {
+ self.player.back();
+ self.redraw()?;
+ }
+ crossterm::InputEvent::Keyboard(crossterm::KeyEvent::Char(
+ '>',
+ )) => {
+ self.player.forward();
+ self.redraw()?;
+ }
_ => {}
}
Ok(false)
}
+
+ fn redraw(&self) -> Result<()> {
+ let frame = if let Some(frame) = self.player.current_frame() {
+ frame
+ } else {
+ return Ok(());
+ };
+ self.write(&frame.full)?;
+ Ok(())
+ }
+
+ fn write(&self, data: &[u8]) -> Result<()> {
+ // TODO async
+ let stdout = std::io::stdout();
+ let mut stdout = stdout.lock();
+ stdout.write(data).context(crate::error::WriteTerminal)?;
+ stdout.flush().context(crate::error::FlushTerminal)?;
+ Ok(())
+ }
}
impl PlaySession {
@@ -290,8 +366,10 @@ impl PlaySession {
filename: filename.to_string(),
}
}));
+ let size = crate::term::Size::get()?;
let reader = ttyrec::Reader::new(file);
- self.file = FileState::Open { reader };
+ let parser = vt100::Parser::new(size.rows, size.cols);
+ self.file = FileState::Open { reader, parser };
Ok(component_future::Async::DidWork)
}
_ => Ok(component_future::Async::NothingToDo),
@@ -299,17 +377,31 @@ impl PlaySession {
}
fn poll_read_file(&mut self) -> component_future::Poll<(), Error> {
- if let FileState::Open { reader } = &mut self.file {
+ if let FileState::Open { reader, parser } = &mut self.file {
if let Some(frame) = component_future::try_ready!(reader
.poll_read()
.context(crate::error::ReadTtyrec))
{
+ parser.process(&frame.data);
+
let frame_time = frame.time - reader.offset().unwrap();
let frame_dur = frame_time - self.last_frame_time;
self.last_frame_time = frame_time;
+
+ let full = parser.screen().contents_formatted();
+ let diff = if let Some(last_frame_screen) =
+ &self.last_frame_screen
+ {
+ parser.screen().contents_diff(last_frame_screen)
+ } else {
+ full.clone()
+ };
+
+ self.last_frame_screen = Some(parser.screen().clone());
self.player.add_frame(Frame {
dur: frame_dur,
- data: frame.data,
+ full,
+ diff,
});
} else {
self.file = FileState::Eof;
@@ -327,19 +419,17 @@ impl PlaySession {
.context(crate::error::ToRawMode)?,
);
}
+ if self.alternate_screen.is_none() {
+ self.alternate_screen = Some(
+ crossterm::AlternateScreen::to_alternate(false)
+ .context(crate::error::ToAlternateScreen)?,
+ );
+ }
let e = component_future::try_ready!(self.key_reader.poll()).unwrap();
let quit = self.keypress(&e)?;
if quit {
- self.raw_screen = None;
-
- // TODO async
- let stdout = std::io::stdout();
- let mut stdout = stdout.lock();
- stdout
- .write(b"\x1bc")
- .context(crate::error::WriteTerminal)?;
- stdout.flush().context(crate::error::FlushTerminal)?;
+ self.write(b"\x1b[?25h")?;
Ok(component_future::Async::Ready(()))
} else {
Ok(component_future::Async::DidWork)
@@ -347,16 +437,12 @@ impl PlaySession {
}
fn poll_write_terminal(&mut self) -> component_future::Poll<(), Error> {
- if self.paused.is_some() {
+ if self.player.paused() {
return Ok(component_future::Async::NothingToDo);
}
if let Some(data) = component_future::try_ready!(self.player.poll()) {
- // TODO async
- let stdout = std::io::stdout();
- let mut stdout = stdout.lock();
- stdout.write(&data).context(crate::error::WriteTerminal)?;
- stdout.flush().context(crate::error::FlushTerminal)?;
+ self.write(&data)?;
Ok(component_future::Async::DidWork)
} else if let FileState::Eof = self.file {
Ok(component_future::Async::Ready(()))