diff options
| -rw-r--r-- | Cargo.lock | 42 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/render.rs | 108 |
3 files changed, 101 insertions, 50 deletions
@@ -9,6 +9,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,6 +51,7 @@ dependencies = [ "serde_json", "serde_yaml", "syntect", + "textwrap", "tokio", "tui", ] @@ -842,6 +854,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -1975,6 +1990,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] name = "socket2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2124,6 +2145,17 @@ dependencies = [ ] [[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] name = "thiserror" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2425,6 +2457,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" +dependencies = [ + "hashbrown", + "regex", +] + +[[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -33,6 +33,7 @@ pulldown-cmark = { version = "0.9.2", default-features = false, features = ['sim crossbeam = "0.8.2" tui = "0.19.0" ansi-to-tui = "2.0.0" +textwrap = "0.16.0" [dependencies.syntect] version = "5.0.0" diff --git a/src/render.rs b/src/render.rs index ff71b27..3f050aa 100644 --- a/src/render.rs +++ b/src/render.rs @@ -71,31 +71,23 @@ mod render_stream_tui { ) -> Result<()> { let mut last_tick = Instant::now(); let tick_rate = Duration::from_millis(250); - let mut count_evt = 0; - let mut count_done = 0; loop { + terminal.draw(|f| ui(f, &mut app))?; + if app.ctrlc.load(Ordering::SeqCst) { return Ok(()); } if let Ok(evt) = rx.try_recv() { - count_evt += 1; - app.handle(evt)?; - if count_evt <= 16 { + let want_to_flush = app.handle(evt)?; + if !want_to_flush { continue; - } else { - count_evt = 0; } } - terminal.draw(|f| ui(f, &mut app))?; - - if app.no_interrupt && app.done { - count_done += 1; - if count_done >= 5 { - return Ok(()); - } + if app.is_done() { + return Ok(()); } let timeout = tick_rate @@ -103,26 +95,20 @@ mod render_stream_tui { .unwrap_or_else(|| Duration::from_secs(0)); if crossterm::event::poll(timeout)? { match event::read()? { - Event::Key(key) => { - app.no_interrupt = false; - match key.code { - KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { - app.quit(); - return Ok(()); - } - KeyCode::Down => app.next(), - KeyCode::Up => app.previous(), - _ => {} - } - } - Event::Mouse(ev) => { - app.no_interrupt = false; - match ev.kind { - MouseEventKind::ScrollDown => app.next(), - MouseEventKind::ScrollUp => app.previous(), - _ => {} + Event::Key(key) => match key.code { + KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { + app.quit(); + return Ok(()); } - } + KeyCode::Down => app.next(), + KeyCode::Up => app.previous(), + _ => {} + }, + Event::Mouse(ev) => match ev.kind { + MouseEventKind::ScrollDown => app.next(), + MouseEventKind::ScrollUp => app.previous(), + _ => {} + }, _ => {} } } @@ -137,11 +123,12 @@ mod render_stream_tui { buffer: String, items: Vec<String>, list_state: ListState, - done: bool, + finish_at: Option<Instant>, ctrlc: Arc<AtomicBool>, markdown_render: Arc<MarkdownRender>, - no_interrupt: bool, + auoscroll: bool, num_rows: usize, + num_cols: usize, entry_index: usize, } @@ -150,18 +137,19 @@ mod render_stream_tui { Self { buffer: String::new(), ctrlc, - done: false, + finish_at: None, markdown_render, num_rows: 0, - no_interrupt: true, + num_cols: 0, + auoscroll: true, entry_index: 0, items: vec![], list_state: ListState::default(), } } - pub fn handle(&mut self, evt: RenderStreamEvent) -> Result<()> { - match evt { + pub fn handle(&mut self, evt: RenderStreamEvent) -> Result<bool> { + let want_to_flush = match evt { RenderStreamEvent::Start(question) => { let mut buf = Vec::with_capacity(8); execute!( @@ -172,33 +160,50 @@ mod render_stream_tui { )?; let indicator = String::from_utf8_lossy(&buf); self.buffer.push_str(&format!("{indicator}{question}\n")); + true } RenderStreamEvent::Text(text) => { self.buffer.push_str(&text); + text.contains('\n') } RenderStreamEvent::Done => { - self.done = true; + self.finish_at = Some(Instant::now()); + true } - } - - let markdown = self.markdown_render.render(&self.buffer)?; + }; + let wrapped_buffer = textwrap::wrap(&self.buffer, self.num_cols).join("\n"); + let markdown = self.markdown_render.render(&wrapped_buffer)?; self.items = markdown.split('\n').map(|v| v.to_string()).collect(); - if self.no_interrupt { - self.end(); + + if self.auoscroll { + self.scroll_to_end(); } - Ok(()) + Ok(want_to_flush) } pub fn quit(&mut self) { self.ctrlc.store(true, Ordering::SeqCst); } - pub fn set_rows(&mut self, rows: u16) { + pub fn is_done(&mut self) -> bool { + if self.auoscroll { + if let Some(finish_at) = self.finish_at { + if finish_at.elapsed() >= Duration::from_secs(1) { + return true; + } + } + } + false + } + + pub fn set_size(&mut self, rows: u16, cols: u16) { self.num_rows = rows as usize; + self.num_cols = cols as usize; } pub fn next(&mut self) { + self.auoscroll = false; let index = if self.entry_index < self.num_rows { self.num_rows.min(self.items.len() - 1) } else { @@ -209,6 +214,7 @@ mod render_stream_tui { } pub fn previous(&mut self) { + self.auoscroll = false; let index = self .entry_index .saturating_sub(1) @@ -217,7 +223,7 @@ mod render_stream_tui { self.list_state.select(Some(index)); } - pub fn end(&mut self) { + pub fn scroll_to_end(&mut self) { let len = self.items.len(); self.entry_index = if len < self.num_rows { 0 @@ -246,9 +252,11 @@ mod render_stream_tui { }) .collect(); - app.set_rows(chunks[0].height); + let area = chunks[0]; + + app.set_size(area.height, area.width); let items = List::new(items).highlight_style(Style::default()); - f.render_stateful_widget(items, chunks[0], &mut app.list_state); + f.render_stateful_widget(items, area, &mut app.list_state); } } |
