use crate::app::App;
use crate::conversation::{ConversationContent, ConversationId, ConversationItem, ItemState};
use crate::view::wrap::as_u16;
use ratatui::Terminal;
use ratatui::backend::Backend;
use ratatui::text::{Line, Text};
use ratatui::widgets::{Paragraph, Widget};
use super::Renderer;
use crate::conversation::item_view::content_kind;
#[derive(Default)]
pub(super) struct NativeHistoryCursor {
pub(super) conversation_id: Option<ConversationId>,
pub(super) commit: CommitPoint,
}
#[derive(Clone, Copy, Default)]
pub(super) struct CommitPoint {
pub(super) item_index: usize,
pub(super) rows: usize,
pub(super) width: u16,
pub(super) padding: usize,
}
impl CommitPoint {
pub(super) fn dimensions(self, width: u16, padding: usize) -> (u16, usize) {
if self.rows > 0 { (self.width, self.padding) } else { (width, padding) }
}
}
impl Renderer {
pub(super) fn commit_overflow<B: Backend>(
&mut self,
terminal: &mut Terminal<B>,
app: &App,
width: u16,
capacity: usize,
) -> Result<Vec<Line<'static>>, B::Error> {
let items = app.conversation_items();
let live = self.live_lines(app, width);
let mut overflow = live.len().saturating_sub(capacity);
if overflow == 0 {
return Ok(live);
}
while overflow > 0 {
let commit = self.native_history.commit;
let Some(item) = items.get(commit.item_index) else {
break;
};
let (item_width, item_padding) = commit.dimensions(width, app.content_padding());
let rendered = self.lines(
std::slice::from_ref(item),
items.get(commit.item_index.wrapping_sub(1)).map(content_kind),
item_width,
item_padding,
app.spinner_tick(),
);
let committed = commit.rows.min(rendered.len());
let pending = &rendered[committed..];
match item.state() {
ItemState::Sealed => {
insert_history_lines(terminal, pending)?;
self.stats.history_rows_inserted += pending.len() as u64;
overflow = overflow.saturating_sub(pending.len());
self.native_history.commit = CommitPoint {
item_index: commit.item_index + 1,
..CommitPoint::default()
};
}
ItemState::Open if streams_into_history(item) => {
let take = overflow.min(pending.len().saturating_sub(1));
insert_history_lines(terminal, &pending[..take])?;
self.stats.history_rows_inserted += take as u64;
self.native_history.commit = CommitPoint {
item_index: commit.item_index,
rows: committed + take,
width: item_width,
padding: item_padding,
};
break;
}
ItemState::Open => break,
}
}
Ok(self.live_lines(app, width))
}
}
pub(super) fn streams_into_history(item: &ConversationItem) -> bool {
matches!(item.content(), ConversationContent::Assistant(_))
}
fn insert_history_lines<B: Backend>(terminal: &mut Terminal<B>, lines: &[Line<'static>]) -> Result<(), B::Error> {
for chunk in lines.chunks(usize::from(u16::MAX)) {
let chunk = chunk.to_vec();
terminal.insert_before(as_u16(chunk.len()), move |buffer| {
Paragraph::new(Text::from(chunk)).render(buffer.area, buffer);
})?;
}
Ok(())
}