aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-12-05 17:31:43 -0500
committerJesse Luehrs <doy@tozt.net>2021-12-05 17:31:43 -0500
commit9a3ff64c313a3e0e28cdb7b63e668fa31878fcaf (patch)
treeb3441037baef2b4a64063294320124266ad6655a
parentf4b04b4e35dc21d2e963abf0d4d3aae3bb61b0a2 (diff)
downloadttyrec-bin-9a3ff64c313a3e0e28cdb7b63e668fa31878fcaf.tar.gz
ttyrec-bin-9a3ff64c313a3e0e28cdb7b63e668fa31878fcaf.zip
basic search implementation
-rw-r--r--src/bin/ttyplay/display.rs161
-rw-r--r--src/bin/ttyplay/event.rs47
-rw-r--r--src/bin/ttyplay/frames.rs9
-rw-r--r--src/bin/ttyplay/input.rs133
-rw-r--r--src/bin/ttyplay/timer.rs8
5 files changed, 261 insertions, 97 deletions
diff --git a/src/bin/ttyplay/display.rs b/src/bin/ttyplay/display.rs
index 3ae84e9..c1871cd 100644
--- a/src/bin/ttyplay/display.rs
+++ b/src/bin/ttyplay/display.rs
@@ -1,26 +1,34 @@
use textmode::Textmode as _;
pub struct Display {
+ screen: vt100::Screen,
current_frame: usize,
total_frames: usize,
done_loading: bool,
paused: bool,
show_ui: bool,
show_help: bool,
+ active_search: Option<String>,
}
impl Display {
pub fn new() -> Self {
Self {
+ screen: vt100::Parser::default().screen().clone(),
current_frame: 0,
total_frames: 0,
done_loading: false,
paused: false,
show_ui: true,
show_help: false,
+ active_search: None,
}
}
+ pub fn screen(&mut self, screen: vt100::Screen) {
+ self.screen = screen;
+ }
+
pub fn current_frame(&mut self, idx: usize) {
self.current_frame = idx;
}
@@ -45,75 +53,116 @@ impl Display {
self.show_help = !self.show_help;
}
+ pub fn active_search(&mut self, s: String) {
+ self.active_search = Some(s);
+ }
+
+ pub fn clear_search(&mut self) {
+ self.active_search = None;
+ }
+
pub async fn render(
&self,
- screen: &vt100::Screen,
output: &mut textmode::Output,
) -> anyhow::Result<()> {
- output.clear();
- output.move_to(0, 0);
- output.write(&screen.contents_formatted());
- if self.paused && self.show_ui {
- let pos = output.screen().cursor_position();
+ let pos = output.screen().cursor_position();
- output.move_to(0, 0);
- output.reset_attributes();
- output.set_fgcolor(textmode::color::BLACK);
- if self.done_loading {
- output.set_bgcolor(textmode::color::CYAN);
- } else {
- output.set_bgcolor(textmode::color::RED);
- }
- output.write_str(&format!(
- " {}/{} ",
- self.current_frame + 1,
- self.total_frames
- ));
+ self.render_screen(output);
- let size = output.screen().size();
- output.move_to(0, size.1 - 1);
- output.reset_attributes();
- output.set_fgcolor(textmode::color::BLACK);
- output.set_bgcolor(textmode::color::RED);
- output.write_str("\u{23f8}");
+ if self.paused && self.show_ui {
+ self.render_frame_count(output);
+ self.render_pause_symbol(output);
if self.show_help {
- output.reset_attributes();
- output.set_fgcolor(textmode::color::BLACK);
- output.set_bgcolor(textmode::color::CYAN);
-
- output.move_to(size.0 - 12, size.1 - 23);
- output.write_str(" keys ");
- output.move_to(size.0 - 11, size.1 - 23);
- output.write_str(" q: quit ");
- output.move_to(size.0 - 10, size.1 - 23);
- output.write_str(" space: pause/unpause ");
- output.move_to(size.0 - 9, size.1 - 23);
- output.write_str(" tab: hide/show ui ");
- output.move_to(size.0 - 8, size.1 - 23);
- output.write_str(" h/p: previous frame ");
- output.move_to(size.0 - 7, size.1 - 23);
- output.write_str(" l/n: next frame ");
- output.move_to(size.0 - 6, size.1 - 23);
- output.write_str(" g/0: first frame ");
- output.move_to(size.0 - 5, size.1 - 23);
- output.write_str(" G/$: last frame ");
- output.move_to(size.0 - 4, size.1 - 23);
- output.write_str(" +: increase speed ");
- output.move_to(size.0 - 3, size.1 - 23);
- output.write_str(" -: decrease speed ");
- output.move_to(size.0 - 2, size.1 - 23);
- output.write_str(" =: normal speed ");
- output.move_to(size.0 - 1, size.1 - 23);
- output.write_str(" ?: hide/show help ");
+ self.render_help(output);
}
-
- output.reset_attributes();
- output.move_to(pos.0, pos.1);
}
+ self.render_search(output);
+
+ output.reset_attributes();
+ output.move_to(pos.0, pos.1);
output.refresh().await?;
Ok(())
}
+
+ fn render_screen(&self, output: &mut textmode::Output) {
+ output.clear();
+ output.move_to(0, 0);
+ output.write(&self.screen.contents_formatted());
+ }
+
+ fn render_frame_count(&self, output: &mut textmode::Output) {
+ output.move_to(0, 0);
+ output.reset_attributes();
+ output.set_fgcolor(textmode::color::BLACK);
+ if self.done_loading {
+ output.set_bgcolor(textmode::color::CYAN);
+ } else {
+ output.set_bgcolor(textmode::color::RED);
+ }
+ output.write_str(&format!(
+ " {}/{} ",
+ self.current_frame + 1,
+ self.total_frames
+ ));
+ }
+
+ #[allow(clippy::unused_self)]
+ fn render_pause_symbol(&self, output: &mut textmode::Output) {
+ let size = output.screen().size();
+ output.move_to(0, size.1 - 1);
+ output.reset_attributes();
+ output.set_fgcolor(textmode::color::BLACK);
+ output.set_bgcolor(textmode::color::RED);
+ output.write_str("\u{23f8}");
+ }
+
+ #[allow(clippy::unused_self)]
+ fn render_help(&self, output: &mut textmode::Output) {
+ let size = output.screen().size();
+ output.reset_attributes();
+ output.set_fgcolor(textmode::color::BLACK);
+ output.set_bgcolor(textmode::color::CYAN);
+
+ output.move_to(size.0 - 13, size.1 - 23);
+ output.write_str(" keys ");
+ output.move_to(size.0 - 12, size.1 - 23);
+ output.write_str(" q: quit ");
+ output.move_to(size.0 - 11, size.1 - 23);
+ output.write_str(" space: pause/unpause ");
+ output.move_to(size.0 - 10, size.1 - 23);
+ output.write_str(" tab: hide/show ui ");
+ output.move_to(size.0 - 9, size.1 - 23);
+ output.write_str(" h/p: previous frame ");
+ output.move_to(size.0 - 8, size.1 - 23);
+ output.write_str(" l/n: next frame ");
+ output.move_to(size.0 - 7, size.1 - 23);
+ output.write_str(" g/0: first frame ");
+ output.move_to(size.0 - 6, size.1 - 23);
+ output.write_str(" G/$: last frame ");
+ output.move_to(size.0 - 5, size.1 - 23);
+ output.write_str(" +: increase speed ");
+ output.move_to(size.0 - 4, size.1 - 23);
+ output.write_str(" -: decrease speed ");
+ output.move_to(size.0 - 3, size.1 - 23);
+ output.write_str(" =: normal speed ");
+ output.move_to(size.0 - 2, size.1 - 23);
+ output.write_str(" ?: hide/show help ");
+ }
+
+ fn render_search(&self, output: &mut textmode::Output) {
+ if let Some(search) = &self.active_search {
+ let size = output.screen().size();
+ output.reset_attributes();
+ output.set_fgcolor(textmode::color::BLACK);
+ output.set_bgcolor(textmode::color::CYAN);
+
+ output.move_to(size.0 - 1, 0);
+ output.write_str("/");
+ output.write_str(search);
+ output.write_str(&" ".repeat(size.1 as usize - search.len() - 1));
+ }
+ }
}
diff --git a/src/bin/ttyplay/event.rs b/src/bin/ttyplay/event.rs
index 68900cd..de81220 100644
--- a/src/bin/ttyplay/event.rs
+++ b/src/bin/ttyplay/event.rs
@@ -5,6 +5,9 @@ pub enum Event {
TimerAction(TimerAction),
ToggleUi,
ToggleHelp,
+ ActiveSearch(String),
+ CancelSearch,
+ RunSearch(String),
Quit,
}
@@ -17,6 +20,7 @@ pub enum TimerAction {
SpeedUp,
SlowDown,
DefaultSpeed,
+ Search(String),
Quit,
}
@@ -72,6 +76,9 @@ struct Pending {
timer_actions: std::collections::VecDeque<TimerAction>,
toggle_ui: bool,
toggle_help: bool,
+ active_search: Option<String>,
+ cancel_search: bool,
+ run_search: Option<String>,
quit: bool,
}
@@ -104,6 +111,21 @@ impl Pending {
Event::ToggleHelp => {
self.toggle_help = !self.toggle_help;
}
+ Event::ActiveSearch(s) => {
+ self.active_search = Some(s);
+ self.cancel_search = false;
+ self.run_search = None;
+ }
+ Event::CancelSearch => {
+ self.active_search = None;
+ self.cancel_search = true;
+ self.run_search = None;
+ }
+ Event::RunSearch(s) => {
+ self.active_search = None;
+ self.cancel_search = false;
+ self.run_search = Some(s);
+ }
Event::Quit => {
self.quit = true;
}
@@ -118,6 +140,9 @@ impl Pending {
|| !self.timer_actions.is_empty()
|| self.toggle_ui
|| self.toggle_help
+ || self.active_search.is_some()
+ || self.cancel_search
+ || self.run_search.is_some()
|| self.quit
}
@@ -127,6 +152,13 @@ impl Pending {
Some(Event::Quit)
} else if let Some(action) = self.timer_actions.pop_front() {
Some(Event::TimerAction(action))
+ } else if let Some(active_search) = self.active_search.take() {
+ Some(Event::ActiveSearch(active_search))
+ } else if self.cancel_search {
+ self.cancel_search = false;
+ Some(Event::CancelSearch)
+ } else if let Some(run_search) = self.run_search.take() {
+ Some(Event::RunSearch(run_search))
} else if self.toggle_ui {
self.toggle_ui = false;
Some(Event::ToggleUi)
@@ -154,7 +186,6 @@ pub async fn handle_events(
mut output: textmode::Output,
) -> anyhow::Result<()> {
let mut display = crate::display::Display::new();
- let mut current_screen = vt100::Parser::default().screen().clone();
let events = Reader::new(event_r);
while let Some(event) = events.read().await {
match event {
@@ -163,7 +194,7 @@ pub async fn handle_events(
continue;
}
Event::FrameTransition((idx, screen)) => {
- current_screen = screen;
+ display.screen(screen);
display.current_frame(idx);
}
Event::FrameLoaded(n) => {
@@ -182,11 +213,21 @@ pub async fn handle_events(
Event::ToggleHelp => {
display.toggle_help();
}
+ Event::ActiveSearch(s) => {
+ display.active_search(s);
+ }
+ Event::CancelSearch => {
+ display.clear_search();
+ }
+ Event::RunSearch(s) => {
+ display.clear_search();
+ timer_w.send(TimerAction::Search(s)).await?;
+ }
Event::Quit => {
break;
}
}
- display.render(&current_screen, &mut output).await?;
+ display.render(&mut output).await?;
}
Ok(())
diff --git a/src/bin/ttyplay/frames.rs b/src/bin/ttyplay/frames.rs
index cea05fa..0808c58 100644
--- a/src/bin/ttyplay/frames.rs
+++ b/src/bin/ttyplay/frames.rs
@@ -44,6 +44,15 @@ impl FrameData {
self.frames.len()
}
+ pub fn search(&self, start: usize, query: &str) -> Option<usize> {
+ for (idx, frame) in self.frames.iter().enumerate().skip(start) {
+ if frame.screen.contents().contains(query) {
+ return Some(idx);
+ }
+ }
+ None
+ }
+
pub async fn add_frame(&mut self, frame: Frame) {
self.frames.push(frame);
self.new_frame_w
diff --git a/src/bin/ttyplay/input.rs b/src/bin/ttyplay/input.rs
index d061e24..f46bd0a 100644
--- a/src/bin/ttyplay/input.rs
+++ b/src/bin/ttyplay/input.rs
@@ -3,46 +3,103 @@ pub fn spawn_task(
mut input: textmode::Input,
) {
async_std::task::spawn(async move {
+ let mut search: Option<String> = None;
while let Some(key) = input.read_key().await.unwrap() {
- let event = match key {
- textmode::Key::Char('g' | '0' | ')') => {
- crate::event::Event::TimerAction(
- crate::event::TimerAction::FirstFrame,
- )
+ if let Some(ref mut search_contents) = search {
+ match key {
+ textmode::Key::Char(c) => {
+ search_contents.push(c);
+ event_w
+ .send(crate::event::Event::ActiveSearch(
+ search_contents.clone(),
+ ))
+ .await
+ .unwrap();
+ }
+ textmode::Key::Backspace => {
+ search_contents.pop();
+ event_w
+ .send(crate::event::Event::ActiveSearch(
+ search_contents.clone(),
+ ))
+ .await
+ .unwrap();
+ }
+ textmode::Key::Ctrl(b'm') => {
+ event_w
+ .send(crate::event::Event::RunSearch(
+ search_contents.clone(),
+ ))
+ .await
+ .unwrap();
+ search = None;
+ }
+ textmode::Key::Escape => {
+ event_w
+ .send(crate::event::Event::CancelSearch)
+ .await
+ .unwrap();
+ search = None;
+ }
+ _ => {}
}
- textmode::Key::Char('G' | '$') => {
- crate::event::Event::TimerAction(
- crate::event::TimerAction::LastFrame,
- )
- }
- textmode::Key::Char('l' | 'n') => {
- crate::event::Event::TimerAction(
- crate::event::TimerAction::NextFrame,
- )
- }
- textmode::Key::Char('h' | 'p') => {
- crate::event::Event::TimerAction(
- crate::event::TimerAction::PreviousFrame,
- )
- }
- textmode::Key::Char('q') => crate::event::Event::Quit,
- textmode::Key::Char(' ') => crate::event::Event::TimerAction(
- crate::event::TimerAction::Pause,
- ),
- textmode::Key::Ctrl(b'i') => crate::event::Event::ToggleUi,
- textmode::Key::Char('?') => crate::event::Event::ToggleHelp,
- textmode::Key::Char('+') => crate::event::Event::TimerAction(
- crate::event::TimerAction::SpeedUp,
- ),
- textmode::Key::Char('-') => crate::event::Event::TimerAction(
- crate::event::TimerAction::SlowDown,
- ),
- textmode::Key::Char('=') => crate::event::Event::TimerAction(
- crate::event::TimerAction::DefaultSpeed,
- ),
- _ => continue,
- };
- event_w.send(event).await.unwrap();
+ } else {
+ let event = match key {
+ textmode::Key::Char('g' | '0' | ')') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::FirstFrame,
+ )
+ }
+ textmode::Key::Char('G' | '$') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::LastFrame,
+ )
+ }
+ textmode::Key::Char('l' | 'n') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::NextFrame,
+ )
+ }
+ textmode::Key::Char('h' | 'p') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::PreviousFrame,
+ )
+ }
+ textmode::Key::Char('q') => crate::event::Event::Quit,
+ textmode::Key::Char(' ') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::Pause,
+ )
+ }
+ textmode::Key::Ctrl(b'i') => {
+ crate::event::Event::ToggleUi
+ }
+ textmode::Key::Char('?') => {
+ crate::event::Event::ToggleHelp
+ }
+ textmode::Key::Char('+') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::SpeedUp,
+ )
+ }
+ textmode::Key::Char('-') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::SlowDown,
+ )
+ }
+ textmode::Key::Char('=') => {
+ crate::event::Event::TimerAction(
+ crate::event::TimerAction::DefaultSpeed,
+ )
+ }
+ textmode::Key::Char('/') => {
+ search = Some("".to_string());
+ crate::event::Event::ActiveSearch("".to_string())
+ }
+ _ => continue,
+ };
+ event_w.send(event).await.unwrap();
+ }
}
});
}
diff --git a/src/bin/ttyplay/timer.rs b/src/bin/ttyplay/timer.rs
index e8e4ce9..eb60185 100644
--- a/src/bin/ttyplay/timer.rs
+++ b/src/bin/ttyplay/timer.rs
@@ -138,6 +138,14 @@ pub fn spawn_task(
- (((now - start_time) * 16) / playback_ratio);
playback_ratio = 16;
}
+ crate::event::TimerAction::Search(s) => {
+ if let Some(new_idx) =
+ frames.lock_arc().await.search(idx + 1, &s)
+ {
+ idx = new_idx;
+ force_update_time = true;
+ }
+ }
crate::event::TimerAction::Quit => break,
},
Res::TimerAction(Err(e)) => panic!("{}", e),