Make `WrapMap` and `DisplayMap` models

Antonio Scandurra created

This removes the need for a lock around in `WrapMap` and also removes
`WrapMap::notifications` because gpui already has a standard way of
notifying when a model updates.

Change summary

gpui/src/app.rs                        |  14 ++
zed/src/editor.rs                      | 112 ++++++++++--------
zed/src/editor/display_map.rs          | 144 ++++++++++++++----------
zed/src/editor/display_map/wrap_map.rs | 168 ++++++++++++++-------------
4 files changed, 246 insertions(+), 192 deletions(-)

Detailed changes

gpui/src/app.rs šŸ”—

@@ -1850,6 +1850,20 @@ impl<M> UpdateModel for ModelContext<'_, M> {
     }
 }
 
+impl<M> Deref for ModelContext<'_, M> {
+    type Target = MutableAppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.app
+    }
+}
+
+impl<M> DerefMut for ModelContext<'_, M> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.app
+    }
+}
+
 pub struct ViewContext<'a, T: ?Sized> {
     app: &'a mut MutableAppContext,
     window_id: usize,

zed/src/editor.rs šŸ”—

@@ -21,7 +21,7 @@ use gpui::{
     ViewContext, WeakViewHandle,
 };
 use parking_lot::Mutex;
-use postage::{prelude::Stream, watch};
+use postage::watch;
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
 use smol::Timer;
@@ -368,7 +368,7 @@ pub enum SelectAction {
 pub struct Editor {
     handle: WeakViewHandle<Self>,
     buffer: ModelHandle<Buffer>,
-    display_map: DisplayMap,
+    display_map: ModelHandle<DisplayMap>,
     selection_set_id: SelectionSetId,
     pending_selection: Option<Selection>,
     next_selection_id: usize,
@@ -417,17 +417,11 @@ impl Editor {
         settings: watch::Receiver<Settings>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        let display_map =
+            cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.borrow().clone(), None, cx));
         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().clone(), None, cx);
-
-        let mut notifications = display_map.notifications();
-        cx.spawn(|this, mut cx| async move {
-            while notifications.recv().await.is_some() {
-                this.update(&mut cx, |_, cx| cx.notify());
-            }
-        })
-        .detach();
+        cx.observe_model(&display_map, Self::on_display_map_changed);
 
         let mut next_selection_id = 0;
         let selection_set_id = buffer.update(cx, |buffer, cx| {
@@ -470,7 +464,7 @@ impl Editor {
         let settings = self.settings.borrow();
 
         Snapshot {
-            display_snapshot: self.display_map.snapshot(cx),
+            display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
             gutter_visible: !self.single_line,
             scroll_position: *self.scroll_position.lock(),
             theme: settings.theme.clone(),
@@ -504,7 +498,7 @@ impl Editor {
         line_height: f32,
         cx: &mut MutableAppContext,
     ) -> bool {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut scroll_position = self.scroll_position.lock();
         let scroll_top = scroll_position.y();
         scroll_position
@@ -564,7 +558,7 @@ impl Editor {
         layouts: &[text_layout::Line],
         cx: &mut MutableAppContext,
     ) -> bool {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut target_left = std::f32::INFINITY;
         let mut target_right = 0.0_f32;
         for selection in self.selections(cx) {
@@ -616,7 +610,7 @@ impl Editor {
             cx.emit(Event::Activate);
         }
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let cursor = display_map.anchor_before(position, Bias::Left);
         let selection = Selection {
             id: post_inc(&mut self.next_selection_id),
@@ -640,7 +634,7 @@ impl Editor {
         scroll_position: Vector2F,
         cx: &mut ViewContext<Self>,
     ) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
         let cursor = display_map.anchor_before(position, Bias::Left);
         if let Some(selection) = self.pending_selection.as_mut() {
@@ -719,7 +713,7 @@ impl Editor {
         T: IntoIterator<Item = &'a Range<DisplayPoint>>,
     {
         let mut selections = Vec::new();
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         for range in ranges {
             let mut start = range.start;
             let mut end = range.end;
@@ -794,7 +788,7 @@ impl Editor {
     pub fn backspace(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
         let mut selections = self.selections(cx.as_ref()).to_vec();
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         {
             let buffer = self.buffer.read(cx);
             for selection in &mut selections {
@@ -816,7 +810,7 @@ impl Editor {
 
     pub fn delete(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx.as_ref()).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -840,7 +834,7 @@ impl Editor {
     pub fn delete_line(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let app = cx.as_ref();
         let buffer = self.buffer.read(app);
 
@@ -926,7 +920,7 @@ impl Editor {
         }
         self.update_selections(selections.clone(), false, cx);
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
 
         let mut edits = Vec::new();
@@ -975,7 +969,7 @@ impl Editor {
     pub fn move_line_up(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let app = cx.as_ref();
         let buffer = self.buffer.read(cx);
 
@@ -1060,7 +1054,7 @@ impl Editor {
     pub fn move_line_down(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         self.start_transaction(cx);
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let app = cx.as_ref();
         let buffer = self.buffer.read(cx);
 
@@ -1275,7 +1269,7 @@ impl Editor {
     }
 
     pub fn move_left(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let app = cx.as_ref();
         let mut selections = self.selections(app).to_vec();
         {
@@ -1299,7 +1293,7 @@ impl Editor {
     }
 
     pub fn select_left(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx.as_ref()).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -1315,7 +1309,7 @@ impl Editor {
     }
 
     pub fn move_right(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx.as_ref()).to_vec();
         {
             for selection in &mut selections {
@@ -1338,7 +1332,7 @@ impl Editor {
     }
 
     pub fn select_right(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx.as_ref()).to_vec();
         {
             let app = cx.as_ref();
@@ -1355,7 +1349,7 @@ impl Editor {
     }
 
     pub fn move_up(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         if self.single_line {
             cx.propagate_action();
         } else {
@@ -1381,7 +1375,7 @@ impl Editor {
     }
 
     pub fn select_up(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx.as_ref()).to_vec();
         {
             let app = cx.as_ref();
@@ -1400,7 +1394,7 @@ impl Editor {
         if self.single_line {
             cx.propagate_action();
         } else {
-            let display_map = self.display_map.snapshot(cx);
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
             let mut selections = self.selections(cx.as_ref()).to_vec();
             {
                 for selection in &mut selections {
@@ -1423,7 +1417,7 @@ impl Editor {
     }
 
     pub fn select_down(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             let app = cx.as_ref();
@@ -1439,7 +1433,7 @@ impl Editor {
     }
 
     pub fn move_to_previous_word_boundary(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             for selection in &mut selections {
@@ -1456,7 +1450,7 @@ impl Editor {
     }
 
     pub fn select_to_previous_word_boundary(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -1479,7 +1473,7 @@ impl Editor {
     }
 
     pub fn move_to_next_word_boundary(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             for selection in &mut selections {
@@ -1496,7 +1490,7 @@ impl Editor {
     }
 
     pub fn select_to_next_word_boundary(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -1519,7 +1513,7 @@ impl Editor {
     }
 
     pub fn move_to_beginning_of_line(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             for selection in &mut selections {
@@ -1540,7 +1534,7 @@ impl Editor {
         toggle_indent: &bool,
         cx: &mut ViewContext<Self>,
     ) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -1564,7 +1558,7 @@ impl Editor {
     }
 
     pub fn move_to_end_of_line(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             for selection in &mut selections {
@@ -1581,7 +1575,7 @@ impl Editor {
     }
 
     pub fn select_to_end_of_line(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         {
             let buffer = self.buffer.read(cx);
@@ -1660,7 +1654,7 @@ impl Editor {
     }
 
     pub fn select_line(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
         let mut selections = self.selections(cx).to_vec();
         let max_point = buffer.max_point();
@@ -1722,7 +1716,7 @@ impl Editor {
     }
 
     fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut selections = self.selections(cx).to_vec();
         let mut state = self.add_selections_state.take().unwrap_or_else(|| {
             let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
@@ -1815,7 +1809,7 @@ impl Editor {
     }
 
     pub fn select_larger_syntax_node(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
 
         let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
@@ -1932,7 +1926,7 @@ impl Editor {
         range: Range<DisplayPoint>,
         cx: &'a mut MutableAppContext,
     ) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
         let selections = &buffer.selection_set(set_id).unwrap().selections;
         let start = display_map.anchor_before(range.start, Bias::Left);
@@ -2056,7 +2050,7 @@ impl Editor {
     pub fn fold(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         let mut fold_ranges = Vec::new();
 
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         for selection in self.selections(cx) {
             let range = selection.display_range(&display_map).sorted();
             let buffer_start_row = range.start.to_buffer_point(&display_map, Bias::Left).row;
@@ -2078,7 +2072,7 @@ impl Editor {
     }
 
     pub fn unfold(&mut self, _: &(), cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.snapshot(cx);
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
         let ranges = self
             .selections(cx)
@@ -2150,7 +2144,7 @@ impl Editor {
 
     fn fold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, cx: &mut ViewContext<Self>) {
         if !ranges.is_empty() {
-            self.display_map.fold(ranges, cx);
+            self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
             *self.autoscroll_requested.lock() = true;
             cx.notify();
         }
@@ -2158,26 +2152,35 @@ impl Editor {
 
     fn unfold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, cx: &mut ViewContext<Self>) {
         if !ranges.is_empty() {
-            self.display_map.unfold(ranges, cx);
+            self.display_map
+                .update(cx, |map, cx| map.unfold(ranges, cx));
             *self.autoscroll_requested.lock() = true;
             cx.notify();
         }
     }
 
     pub fn line_len(&self, display_row: u32, cx: &mut MutableAppContext) -> u32 {
-        self.display_map.snapshot(cx).line_len(display_row)
+        self.display_map
+            .update(cx, |map, cx| map.snapshot(cx))
+            .line_len(display_row)
     }
 
     pub fn longest_row(&self, cx: &mut MutableAppContext) -> u32 {
-        self.display_map.snapshot(cx).longest_row()
+        self.display_map
+            .update(cx, |map, cx| map.snapshot(cx))
+            .longest_row()
     }
 
     pub fn max_point(&self, cx: &mut MutableAppContext) -> DisplayPoint {
-        self.display_map.snapshot(cx).max_point()
+        self.display_map
+            .update(cx, |map, cx| map.snapshot(cx))
+            .max_point()
     }
 
     pub fn text(&self, cx: &mut MutableAppContext) -> String {
-        self.display_map.snapshot(cx).text()
+        self.display_map
+            .update(cx, |map, cx| map.snapshot(cx))
+            .text()
     }
 
     pub fn font_size(&self) -> f32 {
@@ -2185,7 +2188,8 @@ impl Editor {
     }
 
     pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool {
-        self.display_map.set_wrap_width(Some(width), cx)
+        self.display_map
+            .update(cx, |map, cx| map.set_wrap_width(Some(width), cx))
     }
 
     fn next_blink_epoch(&mut self) -> usize {
@@ -2259,6 +2263,10 @@ impl Editor {
             buffer::Event::Reparsed => {}
         }
     }
+
+    fn on_display_map_changed(&mut self, _: ModelHandle<DisplayMap>, cx: &mut ViewContext<Self>) {
+        cx.notify();
+    }
 }
 
 impl Snapshot {

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

@@ -5,8 +5,7 @@ mod wrap_map;
 
 use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint};
 use fold_map::FoldMap;
-use gpui::{ModelHandle, MutableAppContext};
-use postage::prelude::Stream;
+use gpui::{Entity, ModelContext, ModelHandle};
 use std::ops::Range;
 use tab_map::TabMap;
 pub use wrap_map::BufferRows;
@@ -16,7 +15,11 @@ pub struct DisplayMap {
     buffer: ModelHandle<Buffer>,
     fold_map: FoldMap,
     tab_map: TabMap,
-    wrap_map: WrapMap,
+    wrap_map: ModelHandle<WrapMap>,
+}
+
+impl Entity for DisplayMap {
+    type Event = ();
 }
 
 impl DisplayMap {
@@ -24,11 +27,12 @@ impl DisplayMap {
         buffer: ModelHandle<Buffer>,
         settings: Settings,
         wrap_width: Option<f32>,
-        cx: &mut MutableAppContext,
+        cx: &mut ModelContext<Self>,
     ) -> 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);
+        let wrap_map = cx.add_model(|cx| WrapMap::new(snapshot, settings, wrap_width, cx));
+        cx.observe(&wrap_map, |_, _, cx| cx.notify());
         DisplayMap {
             buffer,
             fold_map,
@@ -37,10 +41,12 @@ impl DisplayMap {
         }
     }
 
-    pub fn snapshot(&self, cx: &mut MutableAppContext) -> DisplayMapSnapshot {
+    pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> 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);
+        let wraps_snapshot = self
+            .wrap_map
+            .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
         DisplayMapSnapshot {
             buffer_snapshot: self.buffer.read(cx).snapshot(),
             folds_snapshot,
@@ -52,35 +58,36 @@ impl DisplayMap {
     pub fn fold<T: ToOffset>(
         &mut self,
         ranges: impl IntoIterator<Item = Range<T>>,
-        cx: &mut MutableAppContext,
+        cx: &mut ModelContext<Self>,
     ) {
         let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
-        self.wrap_map.sync(snapshot, edits, cx);
+        self.wrap_map
+            .update(cx, |map, cx| 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);
+        self.wrap_map
+            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
     }
 
     pub fn unfold<T: ToOffset>(
         &mut self,
         ranges: impl IntoIterator<Item = Range<T>>,
-        cx: &mut MutableAppContext,
+        cx: &mut ModelContext<Self>,
     ) {
         let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
-        self.wrap_map.sync(snapshot, edits, cx);
+        self.wrap_map
+            .update(cx, |map, cx| 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);
+        self.wrap_map
+            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
     }
 
-    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut MutableAppContext) -> bool {
-        self.wrap_map.set_wrap_width(width, cx)
-    }
-
-    pub fn notifications(&self) -> impl Stream<Item = ()> {
-        self.wrap_map.notifications()
+    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
+        self.wrap_map
+            .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
 }
 
@@ -282,6 +289,7 @@ mod tests {
         util::RandomCharIter,
     };
     use buffer::History;
+    use gpui::MutableAppContext;
     use rand::prelude::*;
     use std::{env, sync::Arc};
 
@@ -321,11 +329,11 @@ mod tests {
                 Buffer::new(0, text, cx)
             });
             let wrap_width = Some(rng.gen_range(20.0..=100.0));
-            let map = cx.update(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
+            let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
 
             for _op_ix in 0..operations {
                 buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
-                let snapshot = cx.update(|cx| map.snapshot(cx));
+                let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
                 let expected_buffer_rows = (0..=snapshot.max_point().row())
                     .map(|display_row| {
                         DisplayPoint::new(display_row, 0)
@@ -366,9 +374,9 @@ mod tests {
 
         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.update(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
+        let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx));
 
-        let snapshot = cx.update(|cx| map.snapshot(cx));
+        let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
         assert_eq!(
             snapshot
                 .chunks_at(DisplayPoint::new(0, 3))
@@ -389,7 +397,7 @@ mod tests {
             buffer.edit(vec![ix..ix], "and ", cx);
         });
 
-        let snapshot = cx.update(|cx| map.snapshot(cx));
+        let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
         assert_eq!(
             snapshot
                 .chunks_at(DisplayPoint::new(1, 0))
@@ -402,12 +410,14 @@ 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(),
-            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
-            None,
-            cx,
-        );
+        let map = cx.add_model(|cx| {
+            DisplayMap::new(
+                buffer.clone(),
+                Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+                None,
+                cx,
+            )
+        });
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
                 vec![
@@ -421,19 +431,19 @@ mod tests {
         });
 
         assert_eq!(
-            &map.snapshot(cx)
+            &map.update(cx, |map, cx| map.snapshot(cx))
                 .chunks_at(DisplayPoint::new(1, 0))
                 .collect::<String>()[0..10],
             "    b   bb"
         );
         assert_eq!(
-            &map.snapshot(cx)
+            &map.update(cx, |map, cx| map.snapshot(cx))
                 .chunks_at(DisplayPoint::new(1, 2))
                 .collect::<String>()[0..10],
             "  b   bbbb"
         );
         assert_eq!(
-            &map.snapshot(cx)
+            &map.update(cx, |map, cx| map.snapshot(cx))
                 .chunks_at(DisplayPoint::new(1, 6))
                 .collect::<String>()[0..13],
             "  bbbbb\nc   c"
@@ -484,7 +494,7 @@ mod tests {
         });
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 
-        let mut map = cx.update(|cx| {
+        let map = cx.add_model(|cx| {
             DisplayMap::new(
                 buffer,
                 Settings::new(cx.font_cache()).unwrap().with_tab_size(2),
@@ -512,7 +522,9 @@ mod tests {
             ]
         );
 
-        cx.update(|cx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx));
+        map.update(&mut cx, |map, cx| {
+            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
+        });
         assert_eq!(
             cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
             vec![
@@ -579,7 +591,7 @@ mod tests {
             buffer_font_size: 16.0,
             ..Settings::new(&font_cache).unwrap()
         };
-        let mut map = cx.update(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx));
+        let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx));
         assert_eq!(
             cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
             [
@@ -593,7 +605,9 @@ mod tests {
             [("{}\n\n".to_string(), None)]
         );
 
-        cx.update(|cx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx));
+        map.update(&mut cx, |map, cx| {
+            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
+        });
         assert_eq!(
             cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
             [
@@ -611,13 +625,15 @@ mod tests {
         let text = "\n'a', 'α',\t'āœ‹',\t'āŽ', 'šŸ'\n";
         let display_text = "\n'a', 'α',   'āœ‹',    'āŽ', 'šŸ'\n";
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-        let map = DisplayMap::new(
-            buffer.clone(),
-            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
-            None,
-            cx,
-        );
-        let map = map.snapshot(cx);
+        let map = cx.add_model(|cx| {
+            DisplayMap::new(
+                buffer.clone(),
+                Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+                None,
+                cx,
+            )
+        });
+        let map = map.update(cx, |map, cx| map.snapshot(cx));
 
         assert_eq!(map.text(), display_text);
         for (input_column, bias, output_column) in vec![
@@ -650,13 +666,15 @@ mod tests {
     fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
         let text = "āœ…\t\tα\nβ\t\nšŸ€Ī²\t\tγ";
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-        let map = DisplayMap::new(
-            buffer.clone(),
-            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
-            None,
-            cx,
-        );
-        let map = map.snapshot(cx);
+        let map = cx.add_model(|cx| {
+            DisplayMap::new(
+                buffer.clone(),
+                Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+                None,
+                cx,
+            )
+        });
+        let map = map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(map.text(), "āœ…       α\nβ   \nšŸ€Ī²      γ");
 
         let point = Point::new(0, "āœ…\t\t".len() as u32);
@@ -716,23 +734,29 @@ 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(),
-            Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
-            None,
-            cx,
-        );
-        assert_eq!(map.snapshot(cx).max_point(), DisplayPoint::new(1, 11))
+        let map = cx.add_model(|cx| {
+            DisplayMap::new(
+                buffer.clone(),
+                Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
+                None,
+                cx,
+            )
+        });
+        assert_eq!(
+            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
+            DisplayPoint::new(1, 11)
+        )
     }
 
     fn highlighted_chunks<'a>(
         rows: Range<u32>,
-        map: &DisplayMap,
+        map: &ModelHandle<DisplayMap>,
         theme: &'a Theme,
         cx: &mut MutableAppContext,
     ) -> Vec<(String, Option<&'a str>)> {
+        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
         let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
-        for (chunk, style_id) in map.snapshot(cx).highlighted_chunks_for_rows(rows) {
+        for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
             let style_name = theme.syntax_style_name(style_id);
             if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
                 if style_name == *last_style_name {

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

@@ -10,24 +10,22 @@ use crate::{
     util::Bias,
     Settings,
 };
-use gpui::{MutableAppContext, Task};
-use parking_lot::Mutex;
-use postage::{prelude::Stream, sink::Sink, watch};
+use gpui::{Entity, ModelContext, Task};
 use smol::future::yield_now;
 use std::{collections::VecDeque, ops::Range, sync::Arc, time::Duration};
 
-#[derive(Clone)]
-pub struct WrapMap(Arc<Mutex<WrapMapState>>);
-
-struct WrapMapState {
+pub struct WrapMap {
     snapshot: Snapshot,
     pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
     wrap_width: Option<f32>,
     background_task: Option<Task<()>>,
-    updates: (watch::Sender<()>, watch::Receiver<()>),
     line_wrapper: Arc<LineWrapper>,
 }
 
+impl Entity for WrapMap {
+    type Event = ();
+}
+
 #[derive(Clone)]
 pub struct Snapshot {
     tab_snapshot: TabSnapshot,
@@ -78,12 +76,11 @@ impl WrapMap {
         tab_snapshot: TabSnapshot,
         settings: Settings,
         wrap_width: Option<f32>,
-        cx: &mut MutableAppContext,
+        cx: &mut ModelContext<Self>,
     ) -> Self {
-        let this = Self(Arc::new(Mutex::new(WrapMapState {
+        let mut this = Self {
             background_task: None,
             wrap_width: None,
-            updates: watch::channel(),
             pending_edits: Default::default(),
             snapshot: Snapshot::new(tab_snapshot),
             line_wrapper: Arc::new(LineWrapper::new(
@@ -91,49 +88,38 @@ impl WrapMap {
                 cx.font_cache(),
                 settings,
             )),
-        })));
+        };
         this.set_wrap_width(wrap_width, cx);
         this
     }
 
     #[cfg(test)]
     pub fn is_rewrapping(&self) -> bool {
-        self.0.lock().background_task.is_some()
-    }
-
-    pub fn notifications(&self) -> impl Stream<Item = ()> {
-        let state = self.0.lock();
-        let mut rx = state.updates.1.clone();
-        // The first item in the stream always returns what's stored on the watch, but we only want
-        // to receive notifications occurring after calling this method, so we discard the first
-        // item.
-        let _ = rx.blocking_recv();
-        rx
+        self.background_task.is_some()
     }
 
     pub fn sync(
-        &self,
+        &mut self,
         tab_snapshot: TabSnapshot,
         edits: Vec<TabEdit>,
-        cx: &mut MutableAppContext,
+        cx: &mut ModelContext<Self>,
     ) -> Snapshot {
-        self.0.lock().pending_edits.push_back((tab_snapshot, edits));
+        self.pending_edits.push_back((tab_snapshot, edits));
         self.flush_edits(cx);
-        self.0.lock().snapshot.clone()
+        self.snapshot.clone()
     }
 
-    pub fn set_wrap_width(&self, wrap_width: Option<f32>, cx: &mut MutableAppContext) -> bool {
-        let mut state = self.0.lock();
-        if wrap_width == state.wrap_width {
+    pub fn set_wrap_width(&mut self, wrap_width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
+        if wrap_width == self.wrap_width {
             return false;
         }
 
-        state.wrap_width = wrap_width;
-        state.background_task.take();
+        self.wrap_width = wrap_width;
+        self.background_task.take();
 
         if let Some(wrap_width) = wrap_width {
-            let mut new_snapshot = state.snapshot.clone();
-            let line_wrapper = state.line_wrapper.clone();
+            let mut new_snapshot = self.snapshot.clone();
+            let line_wrapper = self.line_wrapper.clone();
             let task = cx.background().spawn(async move {
                 let tab_snapshot = new_snapshot.tab_snapshot.clone();
                 let range = TabPoint::zero()..tab_snapshot.max_point();
@@ -156,19 +142,17 @@ impl WrapMap {
                 .block_with_timeout(Duration::from_millis(5), task)
             {
                 Ok(snapshot) => {
-                    state.snapshot = snapshot;
+                    self.snapshot = snapshot;
                 }
                 Err(wrap_task) => {
-                    let this = self.clone();
-                    state.background_task = Some(cx.spawn(|mut cx| async move {
+                    self.background_task = Some(cx.spawn(|this, mut cx| async move {
                         let snapshot = wrap_task.await;
-                        {
-                            let mut state = this.0.lock();
-                            state.snapshot = snapshot;
-                            state.background_task = None;
-                        }
-                        cx.update(|cx| this.flush_edits(cx));
-                        this.0.lock().updates.0.blocking_send(()).ok();
+                        this.update(&mut cx, |this, cx| {
+                            this.snapshot = snapshot;
+                            this.background_task = None;
+                            this.flush_edits(cx);
+                            cx.notify();
+                        });
                     }));
                 }
             }
@@ -177,26 +161,24 @@ impl WrapMap {
         true
     }
 
-    fn flush_edits(&self, cx: &mut MutableAppContext) {
-        let mut state = self.0.lock();
-
-        while let Some((tab_snapshot, _)) = state.pending_edits.front() {
-            if tab_snapshot.version() <= state.snapshot.tab_snapshot.version() {
-                state.pending_edits.pop_front();
+    fn flush_edits(&mut self, cx: &mut ModelContext<Self>) {
+        while let Some((tab_snapshot, _)) = self.pending_edits.front() {
+            if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() {
+                self.pending_edits.pop_front();
             } else {
                 break;
             }
         }
 
-        if state.pending_edits.is_empty() {
+        if self.pending_edits.is_empty() {
             return;
         }
 
-        if let Some(wrap_width) = state.wrap_width {
-            if state.background_task.is_none() {
-                let pending_edits = state.pending_edits.clone();
-                let mut snapshot = state.snapshot.clone();
-                let line_wrapper = state.line_wrapper.clone();
+        if let Some(wrap_width) = self.wrap_width {
+            if self.background_task.is_none() {
+                let pending_edits = self.pending_edits.clone();
+                let mut snapshot = self.snapshot.clone();
+                let line_wrapper = self.line_wrapper.clone();
 
                 let update_task = cx.background().spawn(async move {
                     for (tab_snapshot, edits) in pending_edits {
@@ -212,35 +194,33 @@ impl WrapMap {
                     .block_with_timeout(Duration::from_micros(500), update_task)
                 {
                     Ok(snapshot) => {
-                        state.snapshot = snapshot;
+                        self.snapshot = snapshot;
                     }
                     Err(update_task) => {
-                        let this = self.clone();
-                        state.background_task = Some(cx.spawn(|mut cx| async move {
+                        self.background_task = Some(cx.spawn(|this, mut cx| async move {
                             let snapshot = update_task.await;
-                            {
-                                let mut state = this.0.lock();
-                                state.snapshot = snapshot;
-                                state.background_task = None;
-                            }
-                            cx.update(|cx| this.flush_edits(cx));
-                            this.0.lock().updates.0.blocking_send(()).ok();
+                            this.update(&mut cx, |this, cx| {
+                                this.snapshot = snapshot;
+                                this.background_task = None;
+                                this.flush_edits(cx);
+                                cx.notify();
+                            });
                         }));
                     }
                 }
             }
         }
 
-        while let Some((tab_snapshot, _)) = state.pending_edits.front() {
-            if tab_snapshot.version() <= state.snapshot.tab_snapshot.version() {
-                state.pending_edits.pop_front();
+        while let Some((tab_snapshot, _)) = self.pending_edits.front() {
+            if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() {
+                self.pending_edits.pop_front();
             } else {
                 break;
             }
         }
 
-        for (tab_snapshot, edits) in state.pending_edits.clone() {
-            state.snapshot.interpolate(tab_snapshot, &edits);
+        for (tab_snapshot, edits) in self.pending_edits.clone() {
+            self.snapshot.interpolate(tab_snapshot, &edits);
         }
     }
 }
@@ -769,7 +749,9 @@ mod tests {
         },
         util::RandomCharIter,
     };
+    use gpui::ModelHandle;
     use rand::prelude::*;
+    use smol::channel;
     use std::env;
 
     #[gpui::test]
@@ -816,7 +798,7 @@ mod tests {
                 folds_snapshot.text()
             );
             log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text());
-            let wrap_map = cx.update(|cx| {
+            let wrap_map = cx.add_model(|cx| {
                 WrapMap::new(
                     tabs_snapshot.clone(),
                     settings.clone(),
@@ -824,17 +806,18 @@ mod tests {
                     cx,
                 )
             });
-            let mut notifications = wrap_map.notifications();
+            let (_observer, notifications) = Observer::new(&wrap_map, &mut cx);
 
             let mut line_wrapper = LineWrapper::new(font_system, &font_cache, settings);
             let unwrapped_text = tabs_snapshot.text();
             let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
 
-            if wrap_map.is_rewrapping() {
-                notifications.recv().await;
+            if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
+                notifications.recv().await.unwrap();
             }
 
-            let snapshot = cx.update(|cx| wrap_map.sync(tabs_snapshot, Vec::new(), cx));
+            let snapshot =
+                wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
             let actual_text = snapshot.text();
             assert_eq!(
                 actual_text, expected_text,
@@ -858,12 +841,15 @@ mod tests {
 
                 let unwrapped_text = tabs_snapshot.text();
                 let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
-                let mut snapshot = cx.update(|cx| wrap_map.sync(tabs_snapshot.clone(), edits, cx));
+                let mut snapshot = wrap_map.update(&mut cx, |map, cx| {
+                    map.sync(tabs_snapshot.clone(), edits, cx)
+                });
                 snapshot.check_invariants(&mut rng);
 
-                if wrap_map.is_rewrapping() {
-                    notifications.recv().await;
-                    snapshot = cx.update(|cx| wrap_map.sync(tabs_snapshot, Vec::new(), cx));
+                if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
+                    notifications.recv().await.unwrap();
+                    snapshot =
+                        wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
                 }
 
                 snapshot.check_invariants(&mut rng);
@@ -952,4 +938,26 @@ mod tests {
             }
         }
     }
+
+    struct Observer;
+
+    impl Entity for Observer {
+        type Event = ();
+    }
+
+    impl Observer {
+        fn new(
+            handle: &ModelHandle<WrapMap>,
+            cx: &mut gpui::TestAppContext,
+        ) -> (ModelHandle<Self>, channel::Receiver<()>) {
+            let (notify_tx, notify_rx) = channel::unbounded();
+            let observer = cx.add_model(|cx| {
+                cx.observe(handle, move |_, _, _| {
+                    let _ = notify_tx.try_send(());
+                });
+                Observer
+            });
+            (observer, notify_rx)
+        }
+    }
 }