use std::{cell::RefCell, collections::HashMap, rc::Rc};
use gpui::{
AnyElement, App, Bounds, Element, ElementId, Font, GlobalElementId, InspectorElementId,
IntoElement, LayoutId, Pixels, TextOverflow, TextStyle, WhiteSpace, Window, px,
};
use super::id::BlockId;
pub(crate) const ESTIMATE: Pixels = px(28.);
#[derive(Default)]
pub(crate) struct HeightIndex {
revision: Option<u64>,
pub(crate) unit_of: HashMap<BlockId, usize>,
ids: Vec<BlockId>,
heights: Vec<Pixels>,
tree: Vec<f32>,
pub(crate) rebuilds: usize,
}
impl HeightIndex {
pub(crate) fn len(&self) -> usize {
self.heights.len()
}
pub(crate) fn is_current(&self, revision: u64) -> bool {
self.revision == Some(revision)
}
pub(crate) fn rebuild<'a>(
&mut self,
revision: u64,
units: impl Iterator<Item = (BlockId, &'a [BlockId])>,
heights: &HashMap<BlockId, Pixels>,
) {
self.revision = Some(revision);
self.rebuilds += 1;
self.unit_of.clear();
self.ids.clear();
self.heights.clear();
for (ix, (id, covered)) in units.enumerate() {
self.ids.push(id);
self.heights
.push(heights.get(&id).copied().unwrap_or(ESTIMATE));
self.unit_of.insert(id, ix);
for block in covered {
self.unit_of.insert(*block, ix);
}
}
let n = self.heights.len();
self.tree = vec![0.; n + 1];
for i in 1..=n {
self.tree[i] += f32::from(self.heights[i - 1]);
let parent = i + (i.isolate_lowest_one());
if parent <= n {
let value = self.tree[i];
self.tree[parent] += value;
}
}
}
pub(crate) fn height(&self, ix: usize) -> Pixels {
self.heights[ix]
}
pub(crate) fn set(&mut self, id: BlockId, height: Pixels) {
let Some(&ix) = self.unit_of.get(&id) else {
return;
};
if self.ids[ix] != id {
return;
}
let delta = f32::from(height) - f32::from(self.heights[ix]);
self.heights[ix] = height;
let mut i = ix + 1;
while i < self.tree.len() {
self.tree[i] += delta;
i += i.isolate_lowest_one();
}
}
pub(crate) fn prefix(&self, ix: usize) -> Pixels {
let mut i = ix.min(self.len());
let mut sum = 0.;
while i > 0 {
sum += self.tree[i];
i -= i.isolate_lowest_one();
}
px(sum)
}
pub(crate) fn find(&self, y: Pixels) -> usize {
let n = self.len();
let mut pos = 0;
let mut rest = f32::from(y);
let mut step = n.checked_next_power_of_two().unwrap_or(0);
if step > n {
step /= 2;
}
while step > 0 {
let next = pos + step;
if next <= n && self.tree[next] <= rest {
pos = next;
rest -= self.tree[next];
}
step /= 2;
}
pos
}
}
pub(crate) struct UnitDesc {
pub(crate) id: BlockId,
pub(crate) block: usize,
pub(crate) node: usize,
pub(crate) node_ix: usize,
pub(crate) items: Option<(std::ops::Range<usize>, Rc<[BlockId]>)>,
pub(crate) flow: gpui_kit::base::text::editor_host::FlowPosition,
}
#[derive(Default)]
pub(crate) struct CullState {
pub(crate) index: HeightIndex,
pub(crate) units: Vec<UnitDesc>,
pub(crate) node_count: usize,
pub(crate) heights: HashMap<BlockId, Pixels>,
pub(crate) visible: Option<(Pixels, Pixels)>,
pub(crate) measured: Vec<(BlockId, usize, Pixels, Pixels)>,
pub(crate) rendered_span: Option<(Pixels, Pixels)>,
pub(crate) rendered: usize,
geometry: Option<Geometry>,
}
#[derive(PartialEq)]
struct Geometry {
width: Pixels,
rem_size: Pixels,
font: Font,
font_size: Pixels,
line_height: Pixels,
white_space: WhiteSpace,
text_overflow: Option<TextOverflow>,
line_clamp: Option<usize>,
}
impl CullState {
pub(crate) fn set_geometry(
&mut self,
width: Pixels,
rem_size: Pixels,
typography: &TextStyle,
) -> bool {
let geometry = Geometry {
width,
rem_size,
font: typography.font(),
font_size: typography.font_size.to_pixels(rem_size),
line_height: typography.line_height_in_pixels(rem_size),
white_space: typography.white_space,
text_overflow: typography.text_overflow.clone(),
line_clamp: typography.line_clamp,
};
let changed = self
.geometry
.as_ref()
.is_some_and(|previous| previous != &geometry);
self.geometry = Some(geometry);
if changed {
self.heights.clear();
self.index.revision = None;
self.visible = None;
self.measured.clear();
return true;
}
false
}
pub(crate) fn finish_frame(&mut self, editor: Bounds<Pixels>, mask: Bounds<Pixels>) -> bool {
let measured = std::mem::take(&mut self.measured);
for (ix, (id, index, top, bottom)) in measured.iter().enumerate() {
let height = match measured.get(ix + 1) {
Some((_, next_index, next_top, _)) if *next_index == index + 1 => *next_top - *top,
_ => self
.heights
.get(id)
.copied()
.unwrap_or(*bottom - *top)
.max(*bottom - *top),
};
self.heights.insert(*id, height);
self.index.set(*id, height);
}
let top = (mask.top() - editor.top()).max(px(0.));
let bottom = (mask.bottom() - editor.top()).min(editor.size.height);
self.visible = Some((top, bottom.max(top)));
match (self.rendered_span, measured.first(), measured.last()) {
(_, Some(first), Some(last)) => {
let span = (first.2 - editor.top(), last.3 - editor.top());
top < span.0 - px(1.) || bottom > span.1 + px(1.)
}
_ => false,
}
}
}
pub(crate) struct Measured {
pub(crate) id: BlockId,
pub(crate) index: usize,
pub(crate) state: Rc<RefCell<CullState>>,
pub(crate) child: Option<AnyElement>,
}
impl IntoElement for Measured {
type Element = Self;
fn into_element(self) -> Self {
self
}
}
impl Element for Measured {
type RequestLayoutState = AnyElement;
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, AnyElement) {
let mut child = self
.child
.take()
.unwrap_or_else(|| gpui::Empty.into_any_element());
(child.request_layout(window, cx), child)
}
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
child: &mut AnyElement,
window: &mut Window,
cx: &mut App,
) {
self.state
.borrow_mut()
.measured
.push((self.id, self.index, bounds.top(), bounds.bottom()));
child.prepaint(window, cx);
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
child: &mut AnyElement,
_: &mut (),
window: &mut Window,
cx: &mut App,
) {
child.paint(window, cx);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn height_index_prefix_find_and_update() {
let ids: Vec<BlockId> = (0..100).map(|_| BlockId::next()).collect();
let mut heights = HashMap::new();
for (i, id) in ids.iter().enumerate() {
heights.insert(*id, px(10. + (i % 3) as f32));
}
let mut index = HeightIndex::default();
index.rebuild(1, ids.iter().map(|id| (*id, &[][..])), &heights);
let naive = |hs: &[f32], ix: usize| hs[..ix].iter().sum::<f32>();
let mut hs: Vec<f32> = (0..100).map(|i| 10. + (i % 3) as f32).collect();
for ix in [0, 1, 7, 50, 99, 100] {
assert!((f32::from(index.prefix(ix)) - naive(&hs, ix)).abs() < 0.01);
}
index.set(ids[42], px(500.));
hs[42] = 500.;
for ix in [42, 43, 100] {
assert!((f32::from(index.prefix(ix)) - naive(&hs, ix)).abs() < 0.01);
}
for y in [0., 5., 10.5, 400., 1000., 1050., 1500., 99999.] {
let found = index.find(px(y));
let want = (0..100).find(|ix| naive(&hs, ix + 1) > y).unwrap_or(100);
assert_eq!(found, want, "y = {y}");
}
assert!(index.is_current(1) && !index.is_current(2));
}
}