mod cache;
mod frame;
mod history;
mod layout;
mod stats;
mod streaming;
mod transcript;
use std::collections::HashMap;
use crate::app::App;
use crate::conversation::{ConversationId, ConversationItemId};
use crate::error::RenderError;
use crate::theme::Theme;
use crate::view::generation::Generation;
use crate::view::syntax::SyntaxHighlighter;
use frame::draw_frame;
use ratatui::Terminal;
use ratatui::backend::Backend;
use cache::RenderCache;
use history::NativeHistoryCursor;
use layout::FrameLayout;
use stats::Lap;
pub use stats::RenderStats;
use streaming::StreamEntry;
pub struct DrawContext<'a> {
pub theme: &'a Theme,
pub highlighter: &'a mut SyntaxHighlighter,
pub theme_generation: Generation,
}
pub struct Renderer {
theme: Theme,
highlighter: SyntaxHighlighter,
theme_generation: Option<Generation>,
render_cache: RenderCache,
native_history: NativeHistoryCursor,
stream_cache: HashMap<ConversationItemId, StreamEntry>,
stats: RenderStats,
}
impl Default for Renderer {
fn default() -> Self {
Self::new()
}
}
impl Renderer {
pub fn new() -> Self {
Self {
theme: Theme::default(),
highlighter: SyntaxHighlighter::new(),
theme_generation: None,
render_cache: RenderCache::default(),
native_history: NativeHistoryCursor::default(),
stream_cache: HashMap::new(),
stats: RenderStats::default(),
}
}
pub fn take_stats(&mut self) -> RenderStats {
let highlight = self.highlighter.take_stats();
RenderStats { highlight, ..std::mem::take(&mut self.stats) }
}
pub fn theme(&self) -> &Theme {
&self.theme
}
fn sync_theme(&mut self, app: &App) {
if self.theme_generation != Some(app.theme_generation()) {
self.theme = app.theme().clone();
self.theme_generation = Some(app.theme_generation());
self.render_cache.clear();
}
}
fn generation(&self) -> Generation {
self.theme_generation.unwrap_or_default()
}
pub fn draw<B: Backend>(&mut self, terminal: &mut Terminal<B>, app: &mut App) -> Result<(), RenderError<B::Error>> {
self.sync_theme(app);
terminal.autoresize().map_err(RenderError::Backend)?;
let area = terminal.get_frame().area();
self.sync_conversation(app.conversation_id());
self.reconcile_history(terminal, app)?;
let lap = Lap::start();
let layout = FrameLayout::new(area, app, self);
let capacity = usize::from(layout.transcript_height).saturating_sub(usize::from(layout.progress_height));
self.stats.ns_layout += lap.ns();
let lap = Lap::start();
let live = self.commit_overflow(terminal, app, area.width, capacity)?;
self.stats.ns_live += lap.ns();
let live = if app.full_screen_active() { Vec::new() } else { live };
let lap = Lap::start();
terminal.draw(|frame| draw_frame(frame, app, self, &layout, &live)).map_err(RenderError::Backend)?;
self.stats.ns_draw += lap.ns();
self.stats.frames += 1;
self.render_cache.current = std::mem::take(&mut self.render_cache.frame);
Ok(())
}
fn context(&mut self) -> DrawContext<'_> {
let theme_generation = self.generation();
DrawContext { theme: &self.theme, highlighter: &mut self.highlighter, theme_generation }
}
fn sync_conversation(&mut self, conversation_id: ConversationId) {
if self.native_history.conversation_id != Some(conversation_id) {
self.native_history =
NativeHistoryCursor { conversation_id: Some(conversation_id), ..NativeHistoryCursor::default() };
self.render_cache.clear();
self.stream_cache.clear();
}
}
}