use ratatui::text::Line;
use crate::tui::app::TranscriptSpacing;
use crate::tui::history::{HistoryCell, TranscriptRenderOptions};
use crate::tui::scrolling::TranscriptLineMeta;
#[derive(Debug)]
pub struct TranscriptViewCache {
width: u16,
version: u64,
options: TranscriptRenderOptions,
lines: Vec<Line<'static>>,
line_meta: Vec<TranscriptLineMeta>,
}
impl TranscriptViewCache {
#[must_use]
pub fn new() -> Self {
Self {
width: 0,
version: 0,
options: TranscriptRenderOptions::default(),
lines: Vec::new(),
line_meta: Vec::new(),
}
}
pub fn ensure(
&mut self,
cells: &[HistoryCell],
width: u16,
version: u64,
options: TranscriptRenderOptions,
) {
if self.width == width && self.version == version && self.options == options {
return;
}
self.width = width;
self.version = version;
self.options = options;
let mut lines = Vec::new();
let mut meta = Vec::new();
for (cell_index, cell) in cells.iter().enumerate() {
let cell_lines = cell.lines_with_options(width, options);
if cell_lines.is_empty() {
continue;
}
for (line_in_cell, line) in cell_lines.into_iter().enumerate() {
lines.push(line);
meta.push(TranscriptLineMeta::CellLine {
cell_index,
line_in_cell,
});
}
if let Some(next_cell) = cells.get(cell_index + 1) {
let spacer_rows = spacer_rows_between(cell, next_cell, options.spacing);
for _ in 0..spacer_rows {
lines.push(Line::from(""));
meta.push(TranscriptLineMeta::Spacer);
}
}
}
self.lines = lines;
self.line_meta = meta;
}
#[must_use]
pub fn lines(&self) -> &[Line<'static>] {
&self.lines
}
#[must_use]
pub fn line_meta(&self) -> &[TranscriptLineMeta] {
&self.line_meta
}
#[must_use]
pub fn total_lines(&self) -> usize {
self.lines.len()
}
}
fn spacer_rows_between(
current: &HistoryCell,
next: &HistoryCell,
spacing: TranscriptSpacing,
) -> usize {
if current.is_stream_continuation() {
return 0;
}
let conversational_gap = match spacing {
TranscriptSpacing::Compact => 0,
TranscriptSpacing::Comfortable => 1,
TranscriptSpacing::Spacious => 2,
};
let secondary_gap = match spacing {
TranscriptSpacing::Compact => 0,
TranscriptSpacing::Comfortable => 1,
TranscriptSpacing::Spacious => 1,
};
if current.is_conversational() && next.is_conversational() {
conversational_gap
} else if matches!(current, HistoryCell::System { .. } | HistoryCell::Tool(_))
|| matches!(next, HistoryCell::System { .. } | HistoryCell::Tool(_))
{
secondary_gap
} else {
0
}
}