summaryrefslogtreecommitdiffstats
path: root/src/render/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/render/mod.rs')
-rw-r--r--src/render/mod.rs120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/render/mod.rs b/src/render/mod.rs
new file mode 100644
index 0000000..d9fcc95
--- /dev/null
+++ b/src/render/mod.rs
@@ -0,0 +1,120 @@
+mod markdown;
+
+pub use self::markdown::MarkdownRender;
+use crate::repl::ReplyStreamEvent;
+
+use anyhow::Result;
+use crossbeam::channel::Receiver;
+use crossterm::{
+ cursor,
+ event::{self, Event, KeyCode, KeyModifiers},
+ queue, style,
+ terminal::{self, disable_raw_mode, enable_raw_mode},
+};
+use std::{
+ io::{self, Stdout, Write},
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ },
+ time::{Duration, Instant},
+};
+use unicode_width::UnicodeWidthStr;
+
+pub fn render_stream(rx: Receiver<ReplyStreamEvent>, ctrlc: Arc<AtomicBool>) -> Result<()> {
+ enable_raw_mode()?;
+ let mut stdout = io::stdout();
+ queue!(stdout, event::DisableMouseCapture)?;
+
+ let ret = render_stream_inner(rx, ctrlc, &mut stdout);
+
+ queue!(stdout, event::DisableMouseCapture)?;
+ disable_raw_mode()?;
+
+ ret
+}
+
+pub fn render_stream_inner(
+ rx: Receiver<ReplyStreamEvent>,
+ ctrlc: Arc<AtomicBool>,
+ writer: &mut Stdout,
+) -> Result<()> {
+ let mut last_tick = Instant::now();
+ let tick_rate = Duration::from_millis(200);
+ let mut buffer = String::new();
+ let mut markdown_render = MarkdownRender::new();
+ let terminal_columns = terminal::size()?.0;
+ loop {
+ if ctrlc.load(Ordering::SeqCst) {
+ return Ok(());
+ }
+
+ if let Ok(evt) = rx.try_recv() {
+ recover_cursor(writer, terminal_columns, &buffer)?;
+
+ match evt {
+ ReplyStreamEvent::Text(text) => {
+ if text.contains('\n') {
+ let text = format!("{buffer}{text}");
+ let mut lines: Vec<&str> = text.split('\n').collect();
+ buffer = lines.pop().unwrap_or_default().to_string();
+ let output = markdown_render.render(&lines.join("\n"));
+ queue!(
+ writer,
+ style::Print(output),
+ style::Print("\n"),
+ style::Print(&buffer),
+ )?;
+ } else {
+ buffer = format!("{buffer}{text}");
+ let output = markdown_render.render_line_stateless(&buffer);
+ queue!(writer, style::Print(&output))?;
+ }
+ writer.flush()?;
+ }
+ ReplyStreamEvent::Done => {
+ let output = markdown_render.render_line_stateless(&buffer);
+ queue!(writer, style::Print(output), style::Print("\n"))?;
+ writer.flush()?;
+ break;
+ }
+ }
+ continue;
+ }
+
+ let timeout = tick_rate
+ .checked_sub(last_tick.elapsed())
+ .unwrap_or_else(|| Duration::from_secs(0));
+ if crossterm::event::poll(timeout)? {
+ if let Event::Key(key) = event::read()? {
+ match key.code {
+ KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
+ ctrlc.store(true, Ordering::SeqCst);
+ return Ok(());
+ }
+ _ => {}
+ }
+ }
+ }
+
+ if last_tick.elapsed() >= tick_rate {
+ last_tick = Instant::now();
+ }
+ }
+ Ok(())
+}
+
+fn recover_cursor(writer: &mut Stdout, terminal_columns: u16, buffer: &str) -> Result<()> {
+ let buffer_rows = (buffer.width() as u16 + terminal_columns - 1) / terminal_columns;
+ let (_, row) = cursor::position()?;
+ if row + 1 >= buffer_rows {
+ queue!(writer, cursor::MoveTo(0, row + 1 - buffer_rows))?;
+ } else {
+ queue!(
+ writer,
+ terminal::ScrollUp(buffer_rows - 1 - row),
+ cursor::MoveTo(0, 0)
+ )?;
+ }
+ Ok(())
+}