From 069583098f4d168879740b5cb37755cd5a2a568f Mon Sep 17 00:00:00 2001 From: sigoden Date: Sat, 4 Mar 2023 22:18:42 +0800 Subject: refactor: no need tui, just render stream directly (#8) --- src/render.rs | 265 +++++++--------------------------------------------------- 1 file changed, 32 insertions(+), 233 deletions(-) (limited to 'src/render.rs') diff --git a/src/render.rs b/src/render.rs index 19dd470..4af0d59 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,24 +1,14 @@ use anyhow::Result; use crossbeam::channel::Receiver; -use crossterm::{ - event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers}, - execute, - style::{self, Color}, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; use mdcat::{ push_tty, terminal::{TerminalProgram, TerminalSize}, Environment, ResourceAccess, Settings, }; use pulldown_cmark::Parser; -use std::{ - io, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::{Duration, Instant}, +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, }; use syntect::parsing::SyntaxSet; @@ -29,235 +19,44 @@ pub fn render_stream( ctrlc: Arc, markdown_render: Arc, ) -> Result<()> { - // setup terminal - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; - let backend = tui::backend::CrosstermBackend::new(stdout); - let mut terminal = tui::Terminal::new(backend)?; - - // create app and run it - let app = render_stream_tui::App::new(ctrlc, markdown_render); - let res = render_stream_tui::run(&mut terminal, app, rx); - - // restore terminal - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; - - res -} - -mod render_stream_tui { - use super::*; - use ansi_to_tui::IntoText; - use crossterm::event::MouseEventKind; - use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout}, - style::Style, - widgets::{List, ListItem, ListState}, - Frame, Terminal, - }; - - pub fn run( - terminal: &mut Terminal, - mut app: App, - rx: Receiver, - ) -> Result<()> { - let mut last_tick = Instant::now(); - let tick_rate = Duration::from_millis(250); - - loop { - terminal.draw(|f| ui(f, &mut app))?; - - if app.ctrlc.load(Ordering::SeqCst) { - return Ok(()); - } - - if let Ok(evt) = rx.try_recv() { - let want_to_flush = app.handle(evt)?; - if !want_to_flush { - continue; - } - } - - if app.is_done() { - return Ok(()); - } - - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or_else(|| Duration::from_secs(0)); - if crossterm::event::poll(timeout)? { - match event::read()? { - 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(), - _ => {} - }, - _ => {} - } - } - if last_tick.elapsed() >= tick_rate { - app.on_tick(); - last_tick = Instant::now(); - } - } - } - - pub struct App { - buffer: String, - items: Vec, - list_state: ListState, - finish_at: Option, - ctrlc: Arc, - markdown_render: Arc, - auto_scroll: bool, - num_rows: usize, - num_cols: usize, - entry_index: usize, - } - - impl App { - pub fn new(ctrlc: Arc, markdown_render: Arc) -> Self { - Self { - buffer: String::new(), - ctrlc, - finish_at: None, - markdown_render, - num_rows: 0, - num_cols: 0, - auto_scroll: true, - entry_index: 0, - items: vec![], - list_state: ListState::default(), - } + let mut buffer = String::new(); + let mut line_index = 0; + loop { + if ctrlc.load(Ordering::SeqCst) { + return Ok(()); } - - pub fn handle(&mut self, evt: RenderStreamEvent) -> Result { - let want_to_flush = match evt { - RenderStreamEvent::Start(question) => { - let mut buf = Vec::with_capacity(8); - execute!( - buf, - style::SetForegroundColor(Color::Cyan), - style::Print("〉"), - style::ResetColor - )?; - let indicator = String::from_utf8_lossy(&buf); - self.buffer.push_str(&format!("{indicator}{question}\n")); - true - } + if let Ok(evt) = rx.try_recv() { + match evt { + RenderStreamEvent::Start(_) => {} RenderStreamEvent::Text(text) => { - self.buffer.push_str(&text); - text.contains('\n') + buffer.push_str(&text); + if text.contains('\n') { + let markdown = markdown_render.render(&buffer)?; + let lines: Vec<&str> = markdown.lines().collect(); + let (_, print_lines) = lines.split_at(line_index); + let mut print_lines = print_lines.to_vec(); + print_lines.pop(); + if !print_lines.is_empty() { + line_index += print_lines.len(); + dump(print_lines.join("\n").to_string(), 1); + } + } } RenderStreamEvent::Done => { - self.finish_at = Some(Instant::now()); - true - } - }; - 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.auto_scroll { - self.scroll_to_end(); - } - - Ok(want_to_flush) - } - - pub fn quit(&mut self) { - self.ctrlc.store(true, Ordering::SeqCst); - } - - pub fn is_done(&mut self) -> bool { - if self.auto_scroll { - if let Some(finish_at) = self.finish_at { - if finish_at.elapsed() >= Duration::from_secs(1) { - return true; - } + let markdown = markdown_render.render(&buffer)?; + let tail = markdown + .lines() + .skip(line_index) + .collect::>() + .join("\n"); + dump(tail, 2); + break; } } - 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.auto_scroll = false; - let index = if self.entry_index < self.num_rows { - self.num_rows.min(self.items.len() - 1) - } else { - self.entry_index + 1 - }; - self.entry_index = index; - self.list_state.select(Some(index)); - } - - pub fn previous(&mut self) { - self.auto_scroll = false; - let index = self - .entry_index - .saturating_sub(1) - .min(self.items.len().saturating_sub(self.num_rows + 1)); - self.entry_index = index; - self.list_state.select(Some(index)); - } - - pub fn scroll_to_end(&mut self) { - let len = self.items.len(); - self.entry_index = if len < self.num_rows { - 0 - } else { - len - self.num_rows - }; - self.list_state.select(Some(len - 1)) - } - - pub fn on_tick(&mut self) {} } - fn ui(f: &mut Frame, app: &mut App) { - // Create two chunks with equal horizontal screen space - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Percentage(100)].as_ref()) - .split(f.size()); - - let items: Vec = app - .items - .iter() - .map(|line| { - let text = line.into_text().unwrap_or_default(); - ListItem::new(text) - }) - .collect(); - - 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, area, &mut app.list_state); - } + Ok(()) } pub struct MarkdownRender { -- cgit v1.2.3