Get simple unit test passing for soft-wrap in DisplayMap

Max Brunsfeld created

Change summary

gpui/src/executor.rs                   | 19 +++++
zed/src/editor/display_map.rs          | 87 ++++++++++++++++++++++-----
zed/src/editor/display_map/fold_map.rs | 14 ---
zed/src/editor/display_map/tab_map.rs  | 19 -----
zed/src/editor/display_map/wrap_map.rs | 50 ++++++++--------
5 files changed, 116 insertions(+), 73 deletions(-)

Detailed changes

gpui/src/executor.rs 🔗

@@ -9,6 +9,7 @@ use std::{
     fmt::{self, Debug},
     marker::PhantomData,
     mem,
+    ops::RangeInclusive,
     pin::Pin,
     rc::Rc,
     sync::{
@@ -46,6 +47,7 @@ struct DeterministicState {
     scheduled: Vec<(Runnable, Backtrace)>,
     spawned_from_foreground: Vec<(Runnable, Backtrace)>,
     forbid_parking: bool,
+    block_on_ticks: RangeInclusive<usize>,
 }
 
 pub struct Deterministic {
@@ -62,6 +64,7 @@ impl Deterministic {
                 scheduled: Default::default(),
                 spawned_from_foreground: Default::default(),
                 forbid_parking: false,
+                block_on_ticks: 0..=1000,
             })),
             parker: Default::default(),
         }
@@ -330,13 +333,19 @@ impl Foreground {
 
     pub fn forbid_parking(&self) {
         match self {
-            Self::Platform { .. } => panic!("can't call this method on a platform executor"),
-            Self::Test(_) => panic!("can't call this method on a test executor"),
             Self::Deterministic(executor) => {
                 let mut state = executor.state.lock();
                 state.forbid_parking = true;
                 state.rng = StdRng::seed_from_u64(state.seed);
             }
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    pub fn set_block_on_ticks(&self, range: RangeInclusive<usize>) {
+        match self {
+            Self::Deterministic(executor) => executor.state.lock().block_on_ticks = range,
+            _ => panic!("this method can only be called on a deterministic executor"),
         }
     }
 }
@@ -386,7 +395,11 @@ impl Background {
                 smol::block_on(async move { util::timeout(timeout, future).await.ok() })
             }
             Self::Deterministic(executor) => {
-                let max_ticks = executor.state.lock().rng.gen_range(1..=1000);
+                let max_ticks = {
+                    let mut state = executor.state.lock();
+                    let range = state.block_on_ticks.clone();
+                    state.rng.gen_range(range)
+                };
                 executor.block_on(max_ticks, future)
             }
         }

zed/src/editor/display_map.rs 🔗

@@ -38,10 +38,12 @@ impl DisplayMap {
     pub fn snapshot(&self, cx: &AppContext) -> DisplayMapSnapshot {
         let (folds_snapshot, edits) = self.fold_map.read(cx);
         let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
+        let wraps_snapshot = self.wrap_map.sync(tabs_snapshot.clone(), edits, cx);
         DisplayMapSnapshot {
             buffer_snapshot: self.buffer.read(cx).snapshot(),
             folds_snapshot,
             tabs_snapshot,
+            wraps_snapshot,
         }
     }
 
@@ -51,7 +53,11 @@ impl DisplayMap {
         cx: &AppContext,
     ) {
         let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
-        let edits = fold_map.fold(ranges, cx);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        self.wrap_map.sync(snapshot, edits, cx);
+        let (snapshot, edits) = fold_map.fold(ranges, cx);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        self.wrap_map.sync(snapshot, edits, cx);
     }
 
     pub fn unfold<T: ToOffset>(
@@ -60,7 +66,11 @@ impl DisplayMap {
         cx: &AppContext,
     ) {
         let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
-        let edits = fold_map.unfold(ranges, cx);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        self.wrap_map.sync(snapshot, edits, cx);
+        let (snapshot, edits) = fold_map.unfold(ranges, cx);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        self.wrap_map.sync(snapshot, edits, cx);
     }
 
     pub fn set_wrap_width(&self, width: Option<f32>) {
@@ -72,6 +82,7 @@ pub struct DisplayMapSnapshot {
     buffer_snapshot: buffer::Snapshot,
     folds_snapshot: fold_map::Snapshot,
     tabs_snapshot: tab_map::Snapshot,
+    wraps_snapshot: wrap_map::Snapshot,
 }
 
 impl DisplayMapSnapshot {
@@ -80,11 +91,11 @@ impl DisplayMapSnapshot {
     }
 
     pub fn max_point(&self) -> DisplayPoint {
-        DisplayPoint(self.tabs_snapshot.max_point())
+        DisplayPoint(self.wraps_snapshot.max_point())
     }
 
-    pub fn chunks_at(&self, point: DisplayPoint) -> tab_map::Chunks {
-        self.tabs_snapshot.chunks_at(point.0)
+    pub fn chunks_at(&self, point: DisplayPoint) -> wrap_map::Chunks {
+        self.wraps_snapshot.chunks_at(point.0)
     }
 
     pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> tab_map::HighlightedChunks {
@@ -122,7 +133,7 @@ impl DisplayMapSnapshot {
     }
 
     pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
-        DisplayPoint(self.tabs_snapshot.clip_point(point.0, bias))
+        DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
     }
 
     pub fn folds_in_range<'a, T>(
@@ -194,11 +205,11 @@ impl DisplayMapSnapshot {
 }
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct DisplayPoint(tab_map::OutputPoint);
+pub struct DisplayPoint(wrap_map::OutputPoint);
 
 impl DisplayPoint {
     pub fn new(row: u32, column: u32) -> Self {
-        Self(tab_map::OutputPoint::new(row, column))
+        Self(wrap_map::OutputPoint::new(row, column))
     }
 
     pub fn zero() -> Self {
@@ -222,22 +233,24 @@ impl DisplayPoint {
     }
 
     pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
-        map.folds_snapshot
-            .to_input_point(map.tabs_snapshot.to_input_point(self.0, bias).0)
+        let unwrapped_point = map.wraps_snapshot.to_input_point(self.0);
+        let unexpanded_point = map.tabs_snapshot.to_input_point(unwrapped_point, bias).0;
+        map.folds_snapshot.to_input_point(unexpanded_point)
     }
 
     pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
-        map.folds_snapshot
-            .to_input_offset(map.tabs_snapshot.to_input_point(self.0, bias).0)
+        let unwrapped_point = map.wraps_snapshot.to_input_point(self.0);
+        let unexpanded_point = map.tabs_snapshot.to_input_point(unwrapped_point, bias).0;
+        map.folds_snapshot.to_input_offset(unexpanded_point)
     }
 }
 
 impl Point {
     pub fn to_display_point(self, map: &DisplayMapSnapshot) -> DisplayPoint {
-        DisplayPoint(
-            map.tabs_snapshot
-                .to_output_point(map.folds_snapshot.to_output_point(self)),
-        )
+        let folded_point = map.folds_snapshot.to_output_point(self);
+        let expanded_point = map.tabs_snapshot.to_output_point(folded_point);
+        let wrapped_point = map.wraps_snapshot.to_output_point(expanded_point);
+        DisplayPoint(wrapped_point)
     }
 }
 
@@ -258,6 +271,48 @@ mod tests {
     use buffer::History;
     use std::sync::Arc;
 
+    #[gpui::test]
+    async fn test_soft_wraps(mut cx: gpui::TestAppContext) {
+        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
+
+        let font_cache = cx.font_cache();
+
+        let settings = Settings {
+            buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
+            ui_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
+            buffer_font_size: 12.0,
+            ui_font_size: 12.0,
+            tab_size: 4,
+            theme: Arc::new(Theme::default()),
+        };
+        let wrap_width = Some(64.);
+
+        let text = "one two three four five\nsix seven eight";
+        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
+        let map = cx.read(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
+
+        let snapshot = cx.read(|cx| map.snapshot(cx));
+        assert_eq!(
+            snapshot
+                .chunks_at(DisplayPoint::new(0, 3))
+                .collect::<String>(),
+            " two \nthree four \nfive\nsix seven \neight"
+        );
+
+        buffer.update(&mut cx, |buffer, cx| {
+            let ix = buffer.text().find("seven").unwrap();
+            buffer.edit(vec![ix..ix], "and ", cx);
+        });
+
+        let snapshot = cx.read(|cx| map.snapshot(cx));
+        assert_eq!(
+            snapshot
+                .chunks_at(DisplayPoint::new(1, 0))
+                .collect::<String>(),
+            "three four \nfive\nsix and \nseven eight"
+        );
+    }
+
     #[gpui::test]
     fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
         let text = sample_text(6, 6);

zed/src/editor/display_map/fold_map.rs 🔗

@@ -14,7 +14,7 @@ use parking_lot::Mutex;
 use std::{
     cmp::{self, Ordering},
     iter,
-    ops::{Range, Sub},
+    ops::Range,
     sync::atomic::{AtomicUsize, Ordering::SeqCst},
 };
 
@@ -47,14 +47,6 @@ impl OutputPoint {
     }
 }
 
-impl Sub<Self> for OutputPoint {
-    type Output = OutputPoint;
-
-    fn sub(self, other: Self) -> Self::Output {
-        Self(self.0 - other.0)
-    }
-}
-
 pub struct FoldMapWriter<'a>(&'a mut FoldMap);
 
 impl<'a> FoldMapWriter<'a> {
@@ -906,8 +898,8 @@ impl<'a> Iterator for InputRows<'a> {
         }
 
         if self.cursor.item().is_some() {
-            let overshoot = self.output_point - *self.cursor.seek_start();
-            let input_point = *self.cursor.sum_start() + overshoot.0;
+            let overshoot = self.output_point.0 - self.cursor.seek_start().0;
+            let input_point = *self.cursor.sum_start() + overshoot;
             *self.output_point.row_mut() += 1;
             Some(input_point.row)
         } else {

zed/src/editor/display_map/tab_map.rs 🔗

@@ -5,10 +5,7 @@ use super::fold_map::{
     OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot,
 };
 use crate::{editor::rope, settings::StyleId, util::Bias};
-use std::{
-    mem,
-    ops::{Add, AddAssign, Range},
-};
+use std::{mem, ops::Range};
 
 pub struct TabMap(Mutex<Snapshot>);
 
@@ -268,20 +265,6 @@ impl From<super::Point> for OutputPoint {
     }
 }
 
-impl AddAssign<Self> for OutputPoint {
-    fn add_assign(&mut self, rhs: Self) {
-        self.0 += &rhs.0;
-    }
-}
-
-impl Add<Self> for OutputPoint {
-    type Output = OutputPoint;
-
-    fn add(self, other: Self) -> Self::Output {
-        Self(self.0 + other.0)
-    }
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Edit {
     pub old_lines: Range<OutputPoint>,

zed/src/editor/display_map/wrap_map.rs 🔗

@@ -14,12 +14,7 @@ use postage::{
     watch,
 };
 use smol::channel;
-use std::{
-    collections::VecDeque,
-    ops::{AddAssign, Range, Sub},
-    sync::Arc,
-    time::Duration,
-};
+use std::{collections::VecDeque, ops::Range, sync::Arc, time::Duration};
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 pub struct OutputPoint(super::Point);
@@ -50,20 +45,6 @@ impl OutputPoint {
     }
 }
 
-impl AddAssign<Self> for OutputPoint {
-    fn add_assign(&mut self, rhs: Self) {
-        self.0 += &rhs.0;
-    }
-}
-
-impl Sub<Self> for OutputPoint {
-    type Output = OutputPoint;
-
-    fn sub(self, other: Self) -> Self::Output {
-        Self(self.0 - other.0)
-    }
-}
-
 #[derive(Clone)]
 pub struct Snapshot {
     transforms: SumTree<Transform>,
@@ -145,10 +126,10 @@ impl Snapshot {
     }
 
     pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
-        let mut transforms = self.transforms.cursor();
+        let mut transforms = self.transforms.cursor::<OutputPoint, InputPoint>();
         transforms.seek(&point, Bias::Right, &());
         let input_position =
-            *transforms.sum_start() + InputPoint((point - *transforms.seek_start()).0);
+            InputPoint(transforms.sum_start().0 + (point.0 - transforms.seek_start().0));
         let input_chunks = self.input.chunks_at(input_position);
         Chunks {
             input_chunks,
@@ -157,6 +138,26 @@ impl Snapshot {
             input_chunk: "",
         }
     }
+
+    pub fn max_point(&self) -> OutputPoint {
+        self.to_output_point(self.input.max_point())
+    }
+
+    pub fn to_input_point(&self, point: OutputPoint) -> InputPoint {
+        let mut cursor = self.transforms.cursor::<OutputPoint, InputPoint>();
+        cursor.seek(&point, Bias::Right, &());
+        InputPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
+    }
+
+    pub fn to_output_point(&self, point: InputPoint) -> OutputPoint {
+        let mut cursor = self.transforms.cursor::<InputPoint, OutputPoint>();
+        cursor.seek(&point, Bias::Right, &());
+        OutputPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
+    }
+
+    pub fn clip_point(&self, point: OutputPoint, bias: Bias) -> OutputPoint {
+        self.to_output_point(self.input.clip_point(self.to_input_point(point), bias))
+    }
 }
 
 pub struct Chunks<'a> {
@@ -558,13 +559,13 @@ impl sum_tree::Summary for TransformSummary {
 
 impl<'a> sum_tree::Dimension<'a, TransformSummary> for InputPoint {
     fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        *self += InputPoint(summary.input.lines);
+        self.0 += summary.input.lines;
     }
 }
 
 impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputPoint {
     fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        *self += OutputPoint(summary.output.lines);
+        self.0 += summary.output.lines;
     }
 }
 
@@ -578,7 +579,6 @@ mod tests {
         },
         util::RandomCharIter,
     };
-    use futures::StreamExt;
     use gpui::fonts::FontId;
     use rand::prelude::*;
     use std::env;