mirror of
https://github.com/openai/codex.git
synced 2026-11-06 00:30:29 +00:00
Cache stable active-cell layout measurements (#38242)
## What changed - Reuse desired and rendered heights for active transcript cells that report a stable height. - Invalidate the cached layout when the cell identity, revision, width, render mode, or syntax theme changes, and when the active cell is removed. - Continue rebuilding display lines on every frame so animated content stays live, while dynamic-height cells bypass the persistent cache. ## Testing - Add rendering tests for height reuse, bottom-anchored overflow, cache invalidation, animated display lines, and dynamic-height cells. GitOrigin-RevId: 414cc4269bfb6b68105837c82e31787272d335a7
This commit is contained in:
1 parent
74004b5397
commit
3d7bb2dd2e
5 files changed
+345
-7
No files matched your search
@@ -1,6 +1,9 @@
|
||||
//! Render composition for the main chat widget surface.
|
||||
|
||||
use super::transcript::ActiveCellLayoutCache;
|
||||
use super::transcript::ActiveCellLayoutCacheKey;
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
impl ChatWidget {
|
||||
pub(crate) fn as_renderable(&self) -> RenderableItem<'_> {
|
||||
@@ -10,6 +13,18 @@ impl ChatWidget {
|
||||
child: cell.as_ref(),
|
||||
top: 1,
|
||||
right: active_cell_right_reserve,
|
||||
// Externally backed transcript cells can also change viewport height without an
|
||||
// active-cell revision. Spinner cells remain safe because their indicator width
|
||||
// is stable and their display lines are still rebuilt on every frame.
|
||||
persistent_layout: cell.has_stable_transcript_height().then_some(
|
||||
PersistentActiveCellLayout {
|
||||
cache: &self.transcript.active_cell_layout,
|
||||
cell_identity: cell.as_ref() as *const dyn HistoryCell as *const ()
|
||||
as usize,
|
||||
revision: self.transcript.active_cell_revision,
|
||||
render_mode: self.history_render_mode(),
|
||||
},
|
||||
),
|
||||
})),
|
||||
None => RenderableItem::Owned(Box::new(())),
|
||||
};
|
||||
@@ -19,6 +34,7 @@ impl ChatWidget {
|
||||
child: cell,
|
||||
top: 1,
|
||||
right: active_cell_right_reserve,
|
||||
persistent_layout: None,
|
||||
}))
|
||||
}
|
||||
_ => RenderableItem::Owned(Box::new(())),
|
||||
@@ -33,6 +49,7 @@ impl ChatWidget {
|
||||
child: cell,
|
||||
top: 1,
|
||||
right: active_cell_right_reserve,
|
||||
persistent_layout: None,
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -43,6 +60,7 @@ impl ChatWidget {
|
||||
child: cell,
|
||||
top: 1,
|
||||
right: active_cell_right_reserve,
|
||||
persistent_layout: None,
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -66,6 +84,14 @@ struct TranscriptAreaRenderable<'a> {
|
||||
child: &'a dyn HistoryCell,
|
||||
top: u16,
|
||||
right: u16,
|
||||
persistent_layout: Option<PersistentActiveCellLayout<'a>>,
|
||||
}
|
||||
|
||||
struct PersistentActiveCellLayout<'a> {
|
||||
cache: &'a Cell<Option<ActiveCellLayoutCache>>,
|
||||
cell_identity: usize,
|
||||
revision: u64,
|
||||
render_mode: HistoryRenderMode,
|
||||
}
|
||||
|
||||
impl Renderable for TranscriptAreaRenderable<'_> {
|
||||
@@ -76,9 +102,19 @@ impl Renderable for TranscriptAreaRenderable<'_> {
|
||||
let y = if area.height == 0 {
|
||||
0
|
||||
} else {
|
||||
let overflow = paragraph
|
||||
.line_count(area.width)
|
||||
.saturating_sub(usize::from(area.height));
|
||||
let rendered_height = if let Some((cache, mut layout)) = self.layout(area.width) {
|
||||
if let Some(height) = layout.rendered_height {
|
||||
height
|
||||
} else {
|
||||
let height = paragraph.line_count(area.width);
|
||||
layout.rendered_height = Some(height);
|
||||
cache.set(Some(layout));
|
||||
height
|
||||
}
|
||||
} else {
|
||||
paragraph.line_count(area.width)
|
||||
};
|
||||
let overflow = rendered_height.saturating_sub(usize::from(area.height));
|
||||
u16::try_from(overflow).unwrap_or(u16::MAX)
|
||||
};
|
||||
Clear.render(area, buf);
|
||||
@@ -87,11 +123,47 @@ impl Renderable for TranscriptAreaRenderable<'_> {
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
let child_width = width.saturating_sub(self.right).max(1);
|
||||
HistoryCell::desired_height(self.child, child_width) + self.top
|
||||
let desired_height = if let Some((cache, mut layout)) = self.layout(child_width) {
|
||||
if let Some(height) = layout.desired_height {
|
||||
height
|
||||
} else {
|
||||
let height = HistoryCell::desired_height(self.child, child_width);
|
||||
layout.desired_height = Some(height);
|
||||
cache.set(Some(layout));
|
||||
height
|
||||
}
|
||||
} else {
|
||||
HistoryCell::desired_height(self.child, child_width)
|
||||
};
|
||||
desired_height + self.top
|
||||
}
|
||||
}
|
||||
|
||||
impl TranscriptAreaRenderable<'_> {
|
||||
fn layout(
|
||||
&self,
|
||||
width: u16,
|
||||
) -> Option<(&Cell<Option<ActiveCellLayoutCache>>, ActiveCellLayoutCache)> {
|
||||
let persistent = self.persistent_layout.as_ref()?;
|
||||
let key = ActiveCellLayoutCacheKey {
|
||||
cell_identity: persistent.cell_identity,
|
||||
revision: persistent.revision,
|
||||
width,
|
||||
render_mode: persistent.render_mode,
|
||||
syntax_theme_revision: crate::render::highlight::syntax_theme_revision(),
|
||||
};
|
||||
let layout = persistent
|
||||
.cache
|
||||
.get()
|
||||
.filter(|layout| layout.key == key)
|
||||
.unwrap_or(ActiveCellLayoutCache {
|
||||
key,
|
||||
desired_height: None,
|
||||
rendered_height: None,
|
||||
});
|
||||
Some((persistent.cache, layout))
|
||||
}
|
||||
|
||||
fn child_area(&self, area: Rect) -> Rect {
|
||||
let y = area.y.saturating_add(self.top);
|
||||
let height = area.height.saturating_sub(self.top);
|
||||
@@ -104,6 +176,10 @@ impl TranscriptAreaRenderable<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "rendering_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
impl Renderable for ChatWidget {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.as_renderable().render(area, buf);
|
||||
|
||||
Reference in new issue
Block a user