mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
## Why Sizing, rendering, and cursor placement can query the same chat widget layout multiple times in one frame, repeatedly measuring active transcript cells. ## What changed - Build one chat widget renderable tree per frame and reuse it for sizing, rendering, and cursor placement. - Cache each flex child's desired height by width for the lifetime of that tree. - Reuse the bottom pane's renderable directly instead of forwarding each renderable operation through a wrapper. ## Testing - Verify flex layouts measure a child once across frame passes and remeasure it when the width changes. - Verify a chat widget frame measures its active transcript cell once. GitOrigin-RevId: 5ad1a6711f4011c699b5d002b13dc3319cb4db8e
113 lines
2.9 KiB
Rust
113 lines
2.9 KiB
Rust
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use std::cell::Cell;
|
|
|
|
struct HeightRenderable(u16);
|
|
|
|
impl HeightRenderable {
|
|
fn with_height(height: u16) -> Self {
|
|
Self(height)
|
|
}
|
|
}
|
|
|
|
impl Renderable for HeightRenderable {
|
|
fn render(&self, _area: Rect, _buf: &mut Buffer) {}
|
|
|
|
fn desired_height(&self, _width: u16) -> u16 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn flex_redistributes_space_unused_by_short_children() {
|
|
let mut flex = FlexRenderable::new();
|
|
flex.push(
|
|
/*flex*/ 1,
|
|
RenderableItem::Owned(Box::new(HeightRenderable::with_height(/*height*/ 20))),
|
|
);
|
|
flex.push(
|
|
/*flex*/ 1,
|
|
RenderableItem::Owned(Box::new(HeightRenderable::with_height(/*height*/ 2))),
|
|
);
|
|
|
|
let allocated = flex.allocate(Rect::new(
|
|
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 10,
|
|
));
|
|
|
|
assert_eq!(
|
|
allocated
|
|
.into_iter()
|
|
.map(|area| area.height)
|
|
.collect::<Vec<_>>(),
|
|
vec![8, 2],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn flex_reserves_non_flex_space_before_flexible_children() {
|
|
let mut flex = FlexRenderable::new();
|
|
flex.push(
|
|
/*flex*/ 1,
|
|
RenderableItem::Owned(Box::new(HeightRenderable::with_height(/*height*/ 20))),
|
|
);
|
|
flex.push(
|
|
/*flex*/ 0,
|
|
RenderableItem::Owned(Box::new(HeightRenderable::with_height(/*height*/ 2))),
|
|
);
|
|
flex.push(
|
|
/*flex*/ 1,
|
|
RenderableItem::Owned(Box::new(HeightRenderable::with_height(/*height*/ 20))),
|
|
);
|
|
|
|
let allocated = flex.allocate(Rect::new(
|
|
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 10,
|
|
));
|
|
|
|
assert_eq!(
|
|
allocated
|
|
.into_iter()
|
|
.map(|area| area.height)
|
|
.collect::<Vec<_>>(),
|
|
vec![4, 2, 4],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn flex_caches_child_height_across_frame_passes() {
|
|
struct CountingRenderable<'a>(&'a Cell<usize>);
|
|
|
|
impl Renderable for CountingRenderable<'_> {
|
|
fn render(&self, _area: Rect, _buf: &mut Buffer) {}
|
|
|
|
fn desired_height(&self, _width: u16) -> u16 {
|
|
self.0.set(self.0.get() + 1);
|
|
1
|
|
}
|
|
|
|
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
|
Some((area.x, area.y))
|
|
}
|
|
}
|
|
|
|
let calls = Cell::new(0);
|
|
let renderable = CountingRenderable(&calls);
|
|
let mut flex = FlexRenderable::new();
|
|
flex.push(/*flex*/ 1, RenderableItem::Borrowed(&renderable));
|
|
let area = Rect::new(
|
|
/*x*/ 0, /*y*/ 0, /*width*/ 80, /*height*/ 10,
|
|
);
|
|
let mut buf = Buffer::empty(area);
|
|
|
|
assert_eq!(flex.desired_height(area.width), 1);
|
|
flex.render(area, &mut buf);
|
|
assert_eq!(flex.cursor_pos(area), Some((0, 0)));
|
|
assert!(matches!(
|
|
flex.cursor_style(area),
|
|
crossterm::cursor::SetCursorStyle::DefaultUserShape
|
|
));
|
|
assert_eq!(calls.get(), 1);
|
|
|
|
assert_eq!(flex.desired_height(/*width*/ 100), 1);
|
|
assert_eq!(calls.get(), 2);
|
|
}
|