summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock42
-rw-r--r--Cargo.toml1
-rw-r--r--src/render.rs108
3 files changed, 101 insertions, 50 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b975269..d1d8cb3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index c58cc41..7a87218 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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);
}
}