Add WrapMap as a member of DisplayMap

Max Brunsfeld and Nathan Sobo created

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

Change summary

zed/src/editor.rs                      |   3 
zed/src/editor/display_map.rs          |  66 +++++++++---
zed/src/editor/display_map/fold_map.rs |  24 ++--
zed/src/editor/display_map/tab_map.rs  |   5 
zed/src/editor/display_map/wrap_map.rs | 151 +++++++++++----------------
zed/src/lib.rs                         |   3 
zed/src/settings.rs                    |   5 
7 files changed, 136 insertions(+), 121 deletions(-)

Detailed changes

zed/src/editor.rs šŸ”—

@@ -410,7 +410,8 @@ impl Editor {
     ) -> Self {
         cx.observe_model(&buffer, Self::on_buffer_changed);
         cx.subscribe_to_model(&buffer, Self::on_buffer_event);
-        let display_map = DisplayMap::new(buffer.clone(), settings.borrow().tab_size, cx.as_ref());
+        let display_map =
+            DisplayMap::new(buffer.clone(), settings.borrow().clone(), None, cx.as_ref());
 
         let mut next_selection_id = 0;
         let selection_set_id = buffer.update(cx, |buffer, cx| {

zed/src/editor/display_map.rs šŸ”—

@@ -2,35 +2,36 @@ mod fold_map;
 mod tab_map;
 mod wrap_map;
 
-use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
+use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint};
 use fold_map::FoldMap;
 pub use fold_map::InputRows;
 use gpui::{AppContext, ModelHandle};
 use std::ops::Range;
 use tab_map::TabMap;
-// use wrap_map::WrapMap;
+use wrap_map::WrapMap;
 
 pub struct DisplayMap {
     buffer: ModelHandle<Buffer>,
     fold_map: FoldMap,
     tab_map: TabMap,
-    // wrap_map: WrapMap,
+    wrap_map: WrapMap,
 }
 
 impl DisplayMap {
-    pub fn new(buffer: ModelHandle<Buffer>, tab_size: usize, cx: &AppContext) -> Self {
-        let fold_map = FoldMap::new(buffer.clone(), cx);
-        let (snapshot, edits) = fold_map.read(cx);
-        assert_eq!(edits.len(), 0);
-        let tab_map = TabMap::new(snapshot, tab_size);
-        // TODO: take `wrap_width` as a parameter.
-        // let config = { todo!() };
-        // let wrap_map = WrapMap::new(snapshot, config, cx);
+    pub fn new(
+        buffer: ModelHandle<Buffer>,
+        settings: Settings,
+        wrap_width: Option<f32>,
+        cx: &AppContext,
+    ) -> Self {
+        let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
+        let (tab_map, snapshot) = TabMap::new(snapshot, settings.tab_size);
+        let wrap_map = WrapMap::new(snapshot, settings, wrap_width, cx);
         DisplayMap {
             buffer,
             fold_map,
             tab_map,
-            // wrap_map,
+            wrap_map,
         }
     }
 
@@ -61,6 +62,10 @@ impl DisplayMap {
         let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
         let edits = fold_map.unfold(ranges, cx);
     }
+
+    pub fn set_wrap_width(&self, width: Option<f32>) {
+        self.wrap_map.set_wrap_width(width);
+    }
 }
 
 pub struct DisplayMapSnapshot {
@@ -257,7 +262,12 @@ mod tests {
     fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
         let text = sample_text(6, 6);
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-        let map = DisplayMap::new(buffer.clone(), 4, cx.as_ref());
+        let map = DisplayMap::new(
+            buffer.clone(),
+            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+            None,
+            cx.as_ref(),
+        );
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
                 vec![
@@ -334,7 +344,14 @@ mod tests {
         });
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 
-        let mut map = cx.read(|cx| DisplayMap::new(buffer, 2, cx));
+        let mut map = cx.read(|cx| {
+            DisplayMap::new(
+                buffer,
+                Settings::new(cx.font_cache()).unwrap().with_tab_size(2),
+                None,
+                cx,
+            )
+        });
         assert_eq!(
             cx.read(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
             vec![
@@ -399,7 +416,12 @@ mod tests {
         let display_text = "\n'a', 'α',   'āœ‹',    'āŽ', 'šŸ'\n";
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
         let cx = cx.as_ref();
-        let map = DisplayMap::new(buffer.clone(), 4, cx);
+        let map = DisplayMap::new(
+            buffer.clone(),
+            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+            None,
+            cx,
+        );
         let map = map.snapshot(cx);
 
         assert_eq!(map.text(), display_text);
@@ -434,7 +456,12 @@ mod tests {
         let text = "āœ…\t\tα\nβ\t\nšŸ€Ī²\t\tγ";
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
         let cx = cx.as_ref();
-        let map = DisplayMap::new(buffer.clone(), 4, cx);
+        let map = DisplayMap::new(
+            buffer.clone(),
+            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+            None,
+            cx,
+        );
         let map = map.snapshot(cx);
         assert_eq!(map.text(), "āœ…       α\nβ   \nšŸ€Ī²      γ");
 
@@ -495,7 +522,12 @@ mod tests {
     #[gpui::test]
     fn test_max_point(cx: &mut gpui::MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx));
-        let map = DisplayMap::new(buffer.clone(), 4, cx.as_ref());
+        let map = DisplayMap::new(
+            buffer.clone(),
+            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+            None,
+            cx.as_ref(),
+        );
         assert_eq!(
             map.snapshot(cx.as_ref()).max_point(),
             DisplayPoint::new(1, 11)

zed/src/editor/display_map/fold_map.rs šŸ”—

@@ -161,9 +161,9 @@ pub struct FoldMap {
 }
 
 impl FoldMap {
-    pub fn new(buffer_handle: ModelHandle<Buffer>, cx: &AppContext) -> Self {
+    pub fn new(buffer_handle: ModelHandle<Buffer>, cx: &AppContext) -> (Self, Snapshot) {
         let buffer = buffer_handle.read(cx);
-        Self {
+        let this = Self {
             buffer: buffer_handle,
             folds: Default::default(),
             transforms: Mutex::new(SumTree::from_item(
@@ -178,7 +178,9 @@ impl FoldMap {
             )),
             last_sync: Mutex::new(buffer.version()),
             version: AtomicUsize::new(0),
-        }
+        };
+        let (snapshot, _) = this.read(cx);
+        (this, snapshot)
     }
 
     pub fn read(&self, cx: &AppContext) -> (Snapshot, Vec<Edit>) {
@@ -1105,7 +1107,7 @@ mod tests {
     #[gpui::test]
     fn test_basic_folds(cx: &mut gpui::MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
-        let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+        let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
         let (mut writer, _, _) = map.write(cx.as_ref());
         let (snapshot2, edits) = writer.fold(
@@ -1180,7 +1182,7 @@ mod tests {
         let buffer = cx.add_model(|cx| Buffer::new(0, "abcdefghijkl", cx));
 
         {
-            let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+            let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
             let (mut writer, _, _) = map.write(cx.as_ref());
             writer.fold(vec![5..8], cx.as_ref());
@@ -1201,7 +1203,7 @@ mod tests {
         }
 
         {
-            let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+            let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
             // Create two adjacent folds.
             let (mut writer, _, _) = map.write(cx.as_ref());
@@ -1219,7 +1221,7 @@ mod tests {
     #[gpui::test]
     fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
-        let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+        let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
         let (mut writer, _, _) = map.write(cx.as_ref());
         writer.fold(
             vec![
@@ -1237,7 +1239,7 @@ mod tests {
     #[gpui::test]
     fn test_merging_folds_via_edit(cx: &mut gpui::MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
-        let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+        let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
         let (mut writer, _, _) = map.write(cx.as_ref());
         writer.fold(
@@ -1260,7 +1262,7 @@ mod tests {
     #[gpui::test]
     fn test_folds_in_range(cx: &mut gpui::MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
-        let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+        let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
         let buffer = buffer.read(cx);
 
         let (mut writer, _, _) = map.write(cx.as_ref());
@@ -1317,7 +1319,7 @@ mod tests {
                 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
                 Buffer::new(0, text, cx)
             });
-            let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+            let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
             let (mut initial_snapshot, _) = map.read(cx.as_ref());
             let mut snapshot_edits = Vec::new();
@@ -1537,7 +1539,7 @@ mod tests {
         let text = sample_text(6, 6) + "\n";
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 
-        let mut map = FoldMap::new(buffer.clone(), cx.as_ref());
+        let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
 
         let (mut writer, _, _) = map.write(cx.as_ref());
         writer.fold(

zed/src/editor/display_map/tab_map.rs šŸ”—

@@ -13,8 +13,9 @@ use std::{
 pub struct TabMap(Mutex<Snapshot>);
 
 impl TabMap {
-    pub fn new(input: InputSnapshot, tab_size: usize) -> Self {
-        Self(Mutex::new(Snapshot { input, tab_size }))
+    pub fn new(input: InputSnapshot, tab_size: usize) -> (Self, Snapshot) {
+        let snapshot = Snapshot { input, tab_size };
+        (Self(Mutex::new(snapshot.clone())), snapshot)
     }
 
     pub fn sync(

zed/src/editor/display_map/wrap_map.rs šŸ”—

@@ -5,8 +5,9 @@ use crate::{
     editor::Point,
     sum_tree::{self, Cursor, SumTree},
     util::Bias,
+    Settings,
 };
-use gpui::{font_cache::FamilyId, AppContext, FontCache, FontSystem, Task};
+use gpui::{AppContext, FontCache, FontSystem, Task};
 use parking_lot::Mutex;
 use postage::{
     prelude::{Sink, Stream},
@@ -208,13 +209,6 @@ struct State {
     pending_edits: VecDeque<(InputSnapshot, Vec<InputEdit>)>,
 }
 
-#[derive(Clone)]
-pub struct Config {
-    pub wrap_width: f32,
-    pub font_family: FamilyId,
-    pub font_size: f32,
-}
-
 pub struct WrapMap {
     state: Mutex<State>,
     edits_tx: channel::Sender<(InputSnapshot, Vec<InputEdit>)>,
@@ -223,7 +217,12 @@ pub struct WrapMap {
 }
 
 impl WrapMap {
-    pub fn new(input: InputSnapshot, config: Config, cx: &AppContext) -> Self {
+    pub fn new(
+        input: InputSnapshot,
+        settings: Settings,
+        wrap_width: Option<f32>,
+        cx: &AppContext,
+    ) -> Self {
         let font_cache = cx.font_cache().clone();
         let font_system = cx.platform().fonts();
         let snapshot = Snapshot::new(input.clone());
@@ -233,7 +232,8 @@ impl WrapMap {
         let background_task = {
             let snapshot = snapshot.clone();
             cx.background().spawn(async move {
-                let mut wrapper = BackgroundWrapper::new(snapshot, config, font_cache, font_system);
+                let mut wrapper =
+                    BackgroundWrapper::new(snapshot, settings, wrap_width, font_cache, font_system);
                 wrapper.run(input, edits_rx, background_snapshot_tx).await;
             })
         };
@@ -254,11 +254,20 @@ impl WrapMap {
             .try_send((input.clone(), edits.clone()))
             .unwrap();
 
+        let mut snapshot = self.state.lock().snapshot.clone();
+        let mut background_snapshot = self.background_snapshot.clone();
+        cx.background().block_on(Duration::from_millis(5), async {
+            loop {
+                snapshot = background_snapshot.recv().await.unwrap();
+                if snapshot.input.version() == input.version() {
+                    break;
+                }
+            }
+        });
+
         let mut state = &mut *self.state.lock();
-        state.snapshot = self.background_snapshot.borrow().clone();
-        state
-            .pending_edits
-            .push_back((input.clone(), edits.clone()));
+        state.snapshot = snapshot;
+        state.pending_edits.push_back((input, edits));
         while state.pending_edits.front().map_or(false, |(input, _)| {
             input.version() <= state.snapshot.input.version()
         }) {
@@ -267,20 +276,17 @@ impl WrapMap {
         for (input, edits) in &state.pending_edits {
             state.snapshot.interpolate(input.clone(), &edits);
         }
+        state.snapshot.clone()
+    }
 
-        let mut background_snapshot = self.background_snapshot.clone();
-        let next_snapshot = cx
-            .background()
-            .block_on(Duration::from_millis(5), async move {
-                background_snapshot.recv().await;
-            });
-
-        self.state.lock().snapshot.clone()
+    pub fn set_wrap_width(&self, width: Option<f32>) {
+        todo!()
     }
 }
 
 struct BackgroundWrapper {
-    config: Config,
+    settings: Settings,
+    wrap_width: Option<f32>,
     font_cache: Arc<FontCache>,
     font_system: Arc<dyn FontSystem>,
     snapshot: Snapshot,
@@ -289,12 +295,14 @@ struct BackgroundWrapper {
 impl BackgroundWrapper {
     fn new(
         snapshot: Snapshot,
-        config: Config,
+        settings: Settings,
+        wrap_width: Option<f32>,
         font_cache: Arc<FontCache>,
         font_system: Arc<dyn FontSystem>,
     ) -> Self {
         Self {
-            config,
+            settings,
+            wrap_width,
             font_cache,
             font_system,
             snapshot,
@@ -331,10 +339,9 @@ impl BackgroundWrapper {
 
         let font_id = self
             .font_cache
-            .select_font(self.config.font_family, &Default::default())
+            .select_font(self.settings.buffer_font_family, &Default::default())
             .unwrap();
-        let font_size = self.config.font_size;
-        let wrap_width = self.config.wrap_width;
+        let font_size = self.settings.buffer_font_size;
 
         #[derive(Debug)]
         struct RowEdit {
@@ -403,15 +410,17 @@ impl BackgroundWrapper {
                     }
 
                     let mut prev_boundary_ix = 0;
-                    for boundary_ix in self
-                        .font_system
-                        .wrap_line(&line, font_id, font_size, wrap_width)
-                    {
-                        let wrapped = &line[prev_boundary_ix..boundary_ix];
-                        new_transforms
-                            .push_or_extend(Transform::isomorphic(TextSummary::from(wrapped)));
-                        new_transforms.push_or_extend(Transform::newline());
-                        prev_boundary_ix = boundary_ix;
+                    if let Some(wrap_width) = self.wrap_width {
+                        for boundary_ix in self
+                            .font_system
+                            .wrap_line(&line, font_id, font_size, wrap_width)
+                        {
+                            let wrapped = &line[prev_boundary_ix..boundary_ix];
+                            new_transforms
+                                .push_or_extend(Transform::isomorphic(TextSummary::from(wrapped)));
+                            new_transforms.push_or_extend(Transform::newline());
+                            prev_boundary_ix = boundary_ix;
+                        }
                     }
 
                     if prev_boundary_ix < line.len() {
@@ -574,36 +583,6 @@ mod tests {
     use rand::prelude::*;
     use std::env;
 
-    #[gpui::test]
-    async fn test_simple_wraps(mut cx: gpui::TestAppContext) {
-        let text = "one two three four five\nsix seven eight";
-        let font_cache = cx.font_cache().clone();
-        let config = Config {
-            wrap_width: 64.,
-            font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
-            font_size: 14.0,
-        };
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
-        let mut wrap_map = cx.read(|cx| {
-            let fold_map = FoldMap::new(buffer.clone(), cx);
-            let (folds_snapshot, edits) = fold_map.read(cx);
-            let tab_map = TabMap::new(folds_snapshot.clone(), 4);
-            let (tabs_snapshot, _) = tab_map.sync(folds_snapshot, edits);
-            WrapMap::new(tabs_snapshot, config, cx)
-        });
-
-        wrap_map.background_snapshot.next().await;
-        let snapshot = wrap_map.background_snapshot.next().await.unwrap();
-
-        assert_eq!(
-            snapshot
-                .chunks_at(OutputPoint(Point::new(0, 3)))
-                .collect::<String>(),
-            " two \nthree four \nfive\nsix seven \neight"
-        );
-    }
-
     #[gpui::test]
     fn test_random_wraps(cx: &mut gpui::MutableAppContext) {
         let iterations = env::var("ITERATIONS")
@@ -629,23 +608,25 @@ mod tests {
                 log::info!("Initial buffer text: {:?}", text);
                 Buffer::new(0, text, cx)
             });
-            let fold_map = FoldMap::new(buffer.clone(), cx.as_ref());
-            let (folds_snapshot, edits) = fold_map.read(cx.as_ref());
-            let tab_map = TabMap::new(folds_snapshot.clone(), rng.gen_range(1..=4));
-            let (tabs_snapshot, _) = tab_map.sync(folds_snapshot, edits);
+            let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx.as_ref());
+            let (tab_map, tabs_snapshot) =
+                TabMap::new(folds_snapshot.clone(), rng.gen_range(1..=4));
             let font_cache = cx.font_cache().clone();
             let font_system = cx.platform().fonts();
-            let config = Config {
-                wrap_width: rng.gen_range(100.0..=1000.0),
-                font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
-                font_size: 14.0,
+            let wrap_width = rng.gen_range(100.0..=1000.0);
+            let settings = Settings {
+                tab_size: 4,
+                buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
+                buffer_font_size: 14.0,
+                ..Settings::new(&font_cache).unwrap()
             };
             let font_id = font_cache
-                .select_font(config.font_family, &Default::default())
+                .select_font(settings.buffer_font_family, &Default::default())
                 .unwrap();
             let mut wrapper = BackgroundWrapper::new(
                 Snapshot::new(tabs_snapshot.clone()),
-                config.clone(),
+                settings.clone(),
+                Some(wrap_width),
                 font_cache.clone(),
                 font_system.clone(),
             );
@@ -656,12 +637,8 @@ mod tests {
             wrapper.sync(tabs_snapshot.clone(), vec![edit]);
 
             let unwrapped_text = tabs_snapshot.text();
-            let expected_text = wrap_text(
-                &unwrapped_text,
-                config.wrap_width,
-                font_id,
-                font_system.as_ref(),
-            );
+            let expected_text =
+                wrap_text(&unwrapped_text, wrap_width, font_id, font_system.as_ref());
 
             let actual_text = wrapper
                 .snapshot
@@ -683,12 +660,8 @@ mod tests {
                 interpolated_snapshot.check_invariants();
 
                 let unwrapped_text = snapshot.text();
-                let expected_text = wrap_text(
-                    &unwrapped_text,
-                    config.wrap_width,
-                    font_id,
-                    font_system.as_ref(),
-                );
+                let expected_text =
+                    wrap_text(&unwrapped_text, wrap_width, font_id, font_system.as_ref());
                 wrapper.sync(snapshot, edits);
                 wrapper.snapshot.check_invariants();
                 let actual_text = wrapper

zed/src/lib.rs šŸ”—

@@ -17,8 +17,9 @@ mod util;
 pub mod workspace;
 pub mod worktree;
 
+pub use settings::Settings;
 pub struct AppState {
-    pub settings: postage::watch::Receiver<settings::Settings>,
+    pub settings: postage::watch::Receiver<Settings>,
     pub languages: std::sync::Arc<language::LanguageRegistry>,
     pub rpc_router: std::sync::Arc<ForegroundRouter>,
     pub rpc: rpc::Client,

zed/src/settings.rs šŸ”—

@@ -49,6 +49,11 @@ impl Settings {
             ),
         })
     }
+
+    pub fn with_tab_size(mut self, tab_size: usize) -> Self {
+        self.tab_size = tab_size;
+        self
+    }
 }
 
 impl Theme {