Skip to main content

codetether_agent/tui/app/state/
message_cache.rs

1//! Message line cache for render performance.
2//!
3//! Caches rendered `Line<'static>` so the TUI can skip full rebuilds
4//! when only the streaming preview changed.
5
6use ratatui::text::Line;
7
8impl super::AppState {
9    /// Check whether the cached message lines are still valid for the given
10    /// width. Returns `true` when the cache can be reused.
11    pub(crate) fn is_message_cache_valid(&self, max_width: usize) -> bool {
12        self.cached_messages_len == self.messages.len()
13            && self.cached_max_width == max_width
14            && self.cached_streaming_snapshot.as_deref() == Some(&self.streaming_text)
15            && self.cached_processing == self.processing
16    }
17
18    /// Returns cached lines when still valid for `max_width`, or `None`.
19    pub fn get_or_build_message_lines(&mut self, max_width: usize) -> Option<Vec<Line<'static>>> {
20        if self.is_message_cache_valid(max_width) && !self.cached_message_lines.is_empty() {
21            Some(self.cached_message_lines.clone())
22        } else {
23            None
24        }
25    }
26
27    /// Take ownership of the cached lines, clearing the cache.
28    pub(crate) fn take_cached_message_lines(&mut self) -> Vec<Line<'static>> {
29        self.cached_message_lines.drain(..).collect()
30    }
31
32    /// Store rebuilt message lines in the cache.
33    pub(crate) fn store_message_lines(&mut self, lines: Vec<Line<'static>>, max_width: usize) {
34        self.cached_message_lines = lines;
35        self.cached_messages_len = self.messages.len();
36        self.cached_max_width = max_width;
37        self.cached_streaming_snapshot = if self.processing {
38            Some(self.streaming_text.clone())
39        } else {
40            None
41        };
42        self.cached_processing = self.processing;
43    }
44
45    /// Clone only the frozen (non-streaming) prefix of cached lines.
46    ///
47    /// Returns `None` if there is no cache or the frozen prefix is empty.
48    /// Used by the fast-path render that reuses the frozen prefix and
49    /// only rebuilds the streaming suffix.
50    pub(crate) fn clone_frozen_prefix(&self, max_width: usize) -> Option<Vec<Line<'static>>> {
51        if self.cached_frozen_len == 0
52            || self.cached_messages_len != self.messages.len()
53            || self.cached_max_width != max_width
54        {
55            return None;
56        }
57        let frozen: Vec<Line<'static>> = self
58            .cached_message_lines
59            .iter()
60            .take(self.cached_frozen_len)
61            .cloned()
62            .collect();
63        if frozen.is_empty() {
64            None
65        } else {
66            Some(frozen)
67        }
68    }
69
70    /// Store rebuilt message lines along with the frozen-prefix length.
71    pub(crate) fn store_message_lines_with_frozen(
72        &mut self,
73        lines: Vec<Line<'static>>,
74        max_width: usize,
75        frozen_len: usize,
76    ) {
77        self.cached_frozen_len = frozen_len;
78        self.store_message_lines(lines, max_width);
79    }
80}