1mod cache;
17mod frame;
18mod history;
19mod layout;
20mod stats;
21mod streaming;
22mod transcript;
23
24use std::collections::HashMap;
25
26use crate::app::App;
27use crate::conversation::{ConversationId, ConversationItemId};
28use crate::view::generation::Generation;
29use crate::view::syntax::SyntaxHighlighter;
30use crate::theme::Theme;
31use frame::draw_frame;
32use ratatui::Terminal;
33use ratatui::backend::Backend;
34
35pub use stats::RenderStats;
36use cache::RenderCache;
37use history::{CommitPoint, NativeHistoryCursor};
38use layout::FrameLayout;
39use stats::Lap;
40use streaming::StreamEntry;
41
42pub struct DrawContext<'a> {
47 pub theme: &'a Theme,
48 pub highlighter: &'a mut SyntaxHighlighter,
49 pub theme_generation: Generation,
50}
51
52pub struct Renderer {
59 theme: Theme,
60 highlighter: SyntaxHighlighter,
61 theme_generation: Option<Generation>,
62 render_cache: RenderCache,
63 native_history: NativeHistoryCursor,
64 stream_cache: HashMap<ConversationItemId, StreamEntry>,
65 stats: RenderStats,
66}
67
68impl Default for Renderer {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl Renderer {
75 pub fn new() -> Self {
76 Self {
77 theme: Theme::default(),
78 highlighter: SyntaxHighlighter::new(),
79 theme_generation: None,
80 render_cache: RenderCache::default(),
81 native_history: NativeHistoryCursor::default(),
82 stream_cache: HashMap::new(),
83 stats: RenderStats::default(),
84 }
85 }
86
87 pub fn take_stats(&mut self) -> RenderStats {
88 let highlight = self.highlighter.take_stats();
89 RenderStats { highlight, ..std::mem::take(&mut self.stats) }
90 }
91
92 pub fn theme(&self) -> &Theme {
93 &self.theme
94 }
95
96 fn sync_theme(&mut self, app: &App) {
99 if self.theme_generation != Some(app.theme_generation()) {
100 self.theme = app.theme().clone();
101 self.theme_generation = Some(app.theme_generation());
102 self.highlighter.clear();
103 self.render_cache.clear();
104 self.stream_cache.clear();
105 }
106 }
107
108 fn generation(&self) -> Generation {
109 self.theme_generation.unwrap_or_default()
110 }
111
112 pub fn draw<B: Backend>(&mut self, terminal: &mut Terminal<B>, app: &mut App) -> Result<(), B::Error> {
113 self.sync_theme(app);
114 terminal.autoresize()?;
115 let area = terminal.get_frame().area();
116 self.sync_conversation(app.conversation_id());
117
118 let lap = Lap::start();
119 let layout = FrameLayout::new(area, app, self);
120 let capacity =
121 usize::from(layout.transcript_height).saturating_sub(usize::from(layout.progress_height));
122 self.stats.ns_layout += lap.ns();
123
124 let lap = Lap::start();
125 let live = self.commit_overflow(terminal, app, area.width, capacity)?;
126 self.stats.ns_live += lap.ns();
127
128 let live = if app.full_screen_active() { Vec::new() } else { live };
129 let lap = Lap::start();
130 terminal.draw(|frame| draw_frame(frame, app, self, &layout, &live))?;
131 self.stats.ns_draw += lap.ns();
132 self.stats.frames += 1;
133 self.render_cache.current = std::mem::take(&mut self.render_cache.frame);
134 Ok(())
135 }
136
137 fn context(&mut self) -> DrawContext<'_> {
139 let theme_generation = self.generation();
140 DrawContext { theme: &self.theme, highlighter: &mut self.highlighter, theme_generation }
141 }
142
143 fn sync_conversation(&mut self, conversation_id: ConversationId) {
144 if self.native_history.conversation_id != Some(conversation_id) {
145 self.native_history =
146 NativeHistoryCursor { conversation_id: Some(conversation_id), commit: CommitPoint::default() };
147 self.render_cache.clear();
148 self.stream_cache.clear();
149 }
150 }
151}