Implement and test `splice` for `ListState`

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/src/elements/list.rs | 98 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 93 insertions(+), 5 deletions(-)

Detailed changes

gpui/src/elements/list.rs 🔗

@@ -4,7 +4,7 @@ use crate::{
     Element,
 };
 use parking_lot::Mutex;
-use std::sync::Arc;
+use std::{ops::Range, sync::Arc};
 
 use crate::ElementBox;
 
@@ -12,6 +12,7 @@ pub struct List {
     state: ListState,
 }
 
+#[derive(Clone)]
 pub struct ListState(Arc<Mutex<StateInner>>);
 
 struct StateInner {
@@ -26,7 +27,7 @@ enum ElementHeight {
     Ready(f32),
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, PartialEq)]
 struct ElementHeightSummary {
     count: usize,
     pending_count: usize,
@@ -42,6 +43,12 @@ struct PendingCount(usize);
 #[derive(Clone, Debug, Default)]
 struct Height(f32);
 
+impl List {
+    pub fn new(state: ListState) -> Self {
+        Self { state }
+    }
+}
+
 impl Element for List {
     type LayoutState = ();
 
@@ -82,8 +89,7 @@ impl Element for List {
 
         drop(old_heights);
         state.heights = new_heights;
-
-        todo!()
+        (constraint.max, ())
     }
 
     fn paint(
@@ -127,6 +133,33 @@ impl ListState {
             heights,
         })))
     }
+
+    pub fn splice(
+        &self,
+        old_range: Range<usize>,
+        new_elements: impl IntoIterator<Item = ElementBox>,
+    ) {
+        let state = &mut *self.0.lock();
+
+        let mut old_heights = state.heights.cursor::<Count, ()>();
+        let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
+        old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
+
+        let mut len = 0;
+        let old_elements = state.elements.splice(
+            old_range,
+            new_elements.into_iter().map(|e| {
+                len += 1;
+                e
+            }),
+        );
+        drop(old_elements);
+
+        new_heights.extend((0..len).map(|_| ElementHeight::Pending), &());
+        new_heights.push_tree(old_heights.suffix(&()), &());
+        drop(old_heights);
+        state.heights = new_heights;
+    }
 }
 
 impl ElementHeight {
@@ -158,6 +191,7 @@ impl sum_tree::Summary for ElementHeightSummary {
     type Context = ();
 
     fn add_summary(&mut self, summary: &Self, _: &()) {
+        self.count += summary.count;
         self.pending_count += summary.pending_count;
         self.height += summary.height;
     }
@@ -175,6 +209,12 @@ impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Count {
     }
 }
 
+impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Count {
+    fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
+        self.0.cmp(&other.0)
+    }
+}
+
 impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for PendingCount {
     fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) {
         self.0 += summary.pending_count;
@@ -196,10 +236,58 @@ impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::{elements::*, geometry::vector::vec2f};
 
     #[crate::test(self)]
     fn test_layout(cx: &mut crate::MutableAppContext) {
         let mut presenter = cx.build_presenter(0, 20.0);
-        let layout_cx = presenter.layout_cx(cx);
+        let mut layout_cx = presenter.layout_cx(cx);
+        let state = ListState::new(vec![item(20.), item(30.), item(10.)]);
+        let mut list = List::new(state.clone()).boxed();
+
+        let size = list.layout(
+            SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
+            &mut layout_cx,
+        );
+        assert_eq!(size, vec2f(100., 40.));
+        assert_eq!(
+            state.0.lock().heights.summary(),
+            ElementHeightSummary {
+                count: 3,
+                pending_count: 0,
+                height: 60.
+            }
+        );
+
+        state.splice(1..2, vec![item(40.), item(50.)]);
+        state.splice(3..3, vec![item(60.)]);
+        assert_eq!(
+            state.0.lock().heights.summary(),
+            ElementHeightSummary {
+                count: 5,
+                pending_count: 3,
+                height: 30.
+            }
+        );
+        let size = list.layout(
+            SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)),
+            &mut layout_cx,
+        );
+        assert_eq!(size, vec2f(100., 40.));
+        assert_eq!(
+            state.0.lock().heights.summary(),
+            ElementHeightSummary {
+                count: 5,
+                pending_count: 0,
+                height: 180.
+            }
+        );
+    }
+
+    fn item(height: f32) -> ElementBox {
+        ConstrainedBox::new(Empty::new().boxed())
+            .with_height(height)
+            .with_width(100.)
+            .boxed()
     }
 }