Rerender tabs when buffers' file handles change

Nathan Sobo and Nathan Sobo created

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

Change summary

zed/src/editor/buffer/mod.rs           | 916 +++++++++++++++------------
zed/src/editor/buffer_view.rs          |  29 
zed/src/editor/display_map/fold_map.rs |  12 
zed/src/editor/display_map/mod.rs      |   4 
zed/src/workspace/workspace.rs         |  14 
zed/src/worktree.rs                    |  50 +
6 files changed, 581 insertions(+), 444 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -353,15 +353,29 @@ pub struct UndoOperation {
 }
 
 impl Buffer {
-    pub fn new<T: Into<Arc<str>>>(replica_id: ReplicaId, base_text: T) -> Self {
-        Self::build(replica_id, None, History::new(base_text.into()))
-    }
-
-    pub fn from_history(replica_id: ReplicaId, file: FileHandle, history: History) -> Self {
-        Self::build(replica_id, Some(file), history)
-    }
-
-    fn build(replica_id: ReplicaId, file: Option<FileHandle>, history: History) -> Self {
+    pub fn new<T: Into<Arc<str>>>(
+        replica_id: ReplicaId,
+        base_text: T,
+        ctx: &mut ModelContext<Self>,
+    ) -> Self {
+        Self::build(replica_id, None, History::new(base_text.into()), ctx)
+    }
+
+    pub fn from_history(
+        replica_id: ReplicaId,
+        file: FileHandle,
+        history: History,
+        ctx: &mut ModelContext<Self>,
+    ) -> Self {
+        Self::build(replica_id, Some(file), history, ctx)
+    }
+
+    fn build(
+        replica_id: ReplicaId,
+        file: Option<FileHandle>,
+        history: History,
+        ctx: &mut ModelContext<Self>,
+    ) -> Self {
         let mut insertion_splits = HashMap::default();
         let mut fragments = SumTree::new();
 
@@ -410,6 +424,10 @@ impl Buffer {
             });
         }
 
+        if let Some(file) = file.as_ref() {
+            file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged));
+        }
+
         Self {
             file,
             fragments,
@@ -445,6 +463,8 @@ impl Buffer {
 
     pub fn save(&mut self, ctx: &mut ModelContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
         if let Some(file) = &self.file {
+            dbg!(file.path());
+
             let snapshot = self.snapshot();
             let version = self.version.clone();
             let save_task = file.save(snapshot, ctx.as_ref());
@@ -1772,6 +1792,7 @@ pub enum Event {
     Edited(Vec<Edit>),
     Dirtied,
     Saved,
+    FileHandleChanged,
 }
 
 impl Entity for Buffer {
@@ -2305,21 +2326,24 @@ mod tests {
     use std::{cell::RefCell, rc::Rc};
 
     #[test]
-    fn test_edit() -> Result<()> {
-        let mut buffer = Buffer::new(0, "abc");
-        assert_eq!(buffer.text(), "abc");
-        buffer.edit(vec![3..3], "def", None)?;
-        assert_eq!(buffer.text(), "abcdef");
-        buffer.edit(vec![0..0], "ghi", None)?;
-        assert_eq!(buffer.text(), "ghiabcdef");
-        buffer.edit(vec![5..5], "jkl", None)?;
-        assert_eq!(buffer.text(), "ghiabjklcdef");
-        buffer.edit(vec![6..7], "", None)?;
-        assert_eq!(buffer.text(), "ghiabjlcdef");
-        buffer.edit(vec![4..9], "mno", None)?;
-        assert_eq!(buffer.text(), "ghiamnoef");
-
-        Ok(())
+    fn test_edit() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "abc", ctx);
+                assert_eq!(buffer.text(), "abc");
+                buffer.edit(vec![3..3], "def", None).unwrap();
+                assert_eq!(buffer.text(), "abcdef");
+                buffer.edit(vec![0..0], "ghi", None).unwrap();
+                assert_eq!(buffer.text(), "ghiabcdef");
+                buffer.edit(vec![5..5], "jkl", None).unwrap();
+                assert_eq!(buffer.text(), "ghiabjklcdef");
+                buffer.edit(vec![6..7], "", None).unwrap();
+                assert_eq!(buffer.text(), "ghiabjlcdef");
+                buffer.edit(vec![4..9], "mno", None).unwrap();
+                assert_eq!(buffer.text(), "ghiamnoef");
+                buffer
+            });
+        })
     }
 
     #[test]
@@ -2329,8 +2353,8 @@ mod tests {
             let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
             let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
 
-            let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
-            let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
+            let buffer1 = app.add_model(|ctx| Buffer::new(0, "abcdef", ctx));
+            let buffer2 = app.add_model(|ctx| Buffer::new(1, "abcdef", ctx));
             let mut buffer_ops = Vec::new();
             buffer1.update(app, |buffer, ctx| {
                 let buffer_1_events = buffer_1_events.clone();
@@ -2408,187 +2432,207 @@ mod tests {
     #[test]
     fn test_random_edits() {
         for seed in 0..100 {
-            println!("{:?}", seed);
-            let mut rng = &mut StdRng::seed_from_u64(seed);
+            App::test((), |ctx| {
+                println!("{:?}", seed);
+                let mut rng = &mut StdRng::seed_from_u64(seed);
+
+                let reference_string_len = rng.gen_range(0..3);
+                let mut reference_string = RandomCharIter::new(&mut rng)
+                    .take(reference_string_len)
+                    .collect::<String>();
+                ctx.add_model(|ctx| {
+                    let mut buffer = Buffer::new(0, reference_string.as_str(), ctx);
+                    let mut buffer_versions = Vec::new();
+                    for _i in 0..10 {
+                        let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None);
+                        for old_range in old_ranges.iter().rev() {
+                            reference_string = [
+                                &reference_string[0..old_range.start],
+                                new_text.as_str(),
+                                &reference_string[old_range.end..],
+                            ]
+                            .concat();
+                        }
+                        assert_eq!(buffer.text(), reference_string);
 
-            let reference_string_len = rng.gen_range(0..3);
-            let mut reference_string = RandomCharIter::new(&mut rng)
-                .take(reference_string_len)
-                .collect::<String>();
-            let mut buffer = Buffer::new(0, reference_string.as_str());
-            let mut buffer_versions = Vec::new();
-
-            for _i in 0..10 {
-                let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None);
-                for old_range in old_ranges.iter().rev() {
-                    reference_string = [
-                        &reference_string[0..old_range.start],
-                        new_text.as_str(),
-                        &reference_string[old_range.end..],
-                    ]
-                    .concat();
-                }
-                assert_eq!(buffer.text(), reference_string);
+                        if rng.gen_bool(0.25) {
+                            buffer.randomly_undo_redo(rng);
+                            reference_string = buffer.text();
+                        }
 
-                if rng.gen_bool(0.25) {
-                    buffer.randomly_undo_redo(rng);
-                    reference_string = buffer.text();
-                }
+                        {
+                            let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
 
-                {
-                    let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
+                            for (len, rows) in &line_lengths {
+                                for row in rows {
+                                    assert_eq!(buffer.line_len(*row).unwrap(), *len);
+                                }
+                            }
 
-                    for (len, rows) in &line_lengths {
-                        for row in rows {
-                            assert_eq!(buffer.line_len(*row).unwrap(), *len);
+                            let (longest_column, longest_rows) =
+                                line_lengths.iter().next_back().unwrap();
+                            let rightmost_point = buffer.rightmost_point();
+                            assert_eq!(rightmost_point.column, *longest_column);
+                            assert!(longest_rows.contains(&rightmost_point.row));
                         }
-                    }
 
-                    let (longest_column, longest_rows) = line_lengths.iter().next_back().unwrap();
-                    let rightmost_point = buffer.rightmost_point();
-                    assert_eq!(rightmost_point.column, *longest_column);
-                    assert!(longest_rows.contains(&rightmost_point.row));
-                }
+                        for _ in 0..5 {
+                            let end = rng.gen_range(0..buffer.len() + 1);
+                            let start = rng.gen_range(0..end + 1);
+
+                            let line_lengths = line_lengths_in_range(&buffer, start..end);
+                            let (longest_column, longest_rows) =
+                                line_lengths.iter().next_back().unwrap();
+                            let range_sum = buffer.text_summary_for_range(start..end);
+                            assert_eq!(range_sum.rightmost_point.column, *longest_column);
+                            assert!(longest_rows.contains(&range_sum.rightmost_point.row));
+                            let range_text = &buffer.text()[start..end];
+                            assert_eq!(range_sum.chars, range_text.chars().count());
+                            assert_eq!(range_sum.bytes, range_text.len());
+                        }
 
-                for _ in 0..5 {
-                    let end = rng.gen_range(0..buffer.len() + 1);
-                    let start = rng.gen_range(0..end + 1);
-
-                    let line_lengths = line_lengths_in_range(&buffer, start..end);
-                    let (longest_column, longest_rows) = line_lengths.iter().next_back().unwrap();
-                    let range_sum = buffer.text_summary_for_range(start..end);
-                    assert_eq!(range_sum.rightmost_point.column, *longest_column);
-                    assert!(longest_rows.contains(&range_sum.rightmost_point.row));
-                    let range_text = &buffer.text()[start..end];
-                    assert_eq!(range_sum.chars, range_text.chars().count());
-                    assert_eq!(range_sum.bytes, range_text.len());
-                }
+                        if rng.gen_bool(0.3) {
+                            buffer_versions.push(buffer.clone());
+                        }
+                    }
 
-                if rng.gen_bool(0.3) {
-                    buffer_versions.push(buffer.clone());
-                }
-            }
+                    for mut old_buffer in buffer_versions {
+                        let mut delta = 0_isize;
+                        for Edit {
+                            old_range,
+                            new_range,
+                        } in buffer.edits_since(old_buffer.version.clone())
+                        {
+                            let old_len = old_range.end - old_range.start;
+                            let new_len = new_range.end - new_range.start;
+                            let old_start = (old_range.start as isize + delta) as usize;
+                            let new_text: String =
+                                buffer.text_for_range(new_range).unwrap().collect();
+                            old_buffer
+                                .edit(Some(old_start..old_start + old_len), new_text, None)
+                                .unwrap();
+
+                            delta += new_len as isize - old_len as isize;
+                        }
+                        assert_eq!(old_buffer.text(), buffer.text());
+                    }
 
-            for mut old_buffer in buffer_versions {
-                let mut delta = 0_isize;
-                for Edit {
-                    old_range,
-                    new_range,
-                } in buffer.edits_since(old_buffer.version.clone())
-                {
-                    let old_len = old_range.end - old_range.start;
-                    let new_len = new_range.end - new_range.start;
-                    let old_start = (old_range.start as isize + delta) as usize;
-                    let new_text: String = buffer.text_for_range(new_range).unwrap().collect();
-                    old_buffer
-                        .edit(Some(old_start..old_start + old_len), new_text, None)
-                        .unwrap();
-
-                    delta += new_len as isize - old_len as isize;
-                }
-                assert_eq!(old_buffer.text(), buffer.text());
-            }
+                    buffer
+                })
+            });
         }
     }
 
     #[test]
-    fn test_line_len() -> Result<()> {
-        let mut buffer = Buffer::new(0, "");
-        buffer.edit(vec![0..0], "abcd\nefg\nhij", None)?;
-        buffer.edit(vec![12..12], "kl\nmno", None)?;
-        buffer.edit(vec![18..18], "\npqrs\n", None)?;
-        buffer.edit(vec![18..21], "\nPQ", None)?;
-
-        assert_eq!(buffer.line_len(0)?, 4);
-        assert_eq!(buffer.line_len(1)?, 3);
-        assert_eq!(buffer.line_len(2)?, 5);
-        assert_eq!(buffer.line_len(3)?, 3);
-        assert_eq!(buffer.line_len(4)?, 4);
-        assert_eq!(buffer.line_len(5)?, 0);
-        assert!(buffer.line_len(6).is_err());
-
-        Ok(())
+    fn test_line_len() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "", ctx);
+                buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
+                buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
+                buffer.edit(vec![18..18], "\npqrs\n", None).unwrap();
+                buffer.edit(vec![18..21], "\nPQ", None).unwrap();
+
+                assert_eq!(buffer.line_len(0).unwrap(), 4);
+                assert_eq!(buffer.line_len(1).unwrap(), 3);
+                assert_eq!(buffer.line_len(2).unwrap(), 5);
+                assert_eq!(buffer.line_len(3).unwrap(), 3);
+                assert_eq!(buffer.line_len(4).unwrap(), 4);
+                assert_eq!(buffer.line_len(5).unwrap(), 0);
+                assert!(buffer.line_len(6).is_err());
+                buffer
+            });
+        });
     }
 
     #[test]
-    fn test_rightmost_point() -> Result<()> {
-        let mut buffer = Buffer::new(0, "");
-        assert_eq!(buffer.rightmost_point().row, 0);
-        buffer.edit(vec![0..0], "abcd\nefg\nhij", None)?;
-        assert_eq!(buffer.rightmost_point().row, 0);
-        buffer.edit(vec![12..12], "kl\nmno", None)?;
-        assert_eq!(buffer.rightmost_point().row, 2);
-        buffer.edit(vec![18..18], "\npqrs", None)?;
-        assert_eq!(buffer.rightmost_point().row, 2);
-        buffer.edit(vec![10..12], "", None)?;
-        assert_eq!(buffer.rightmost_point().row, 0);
-        buffer.edit(vec![24..24], "tuv", None)?;
-        assert_eq!(buffer.rightmost_point().row, 4);
-
-        println!("{:?}", buffer.text());
-
-        Ok(())
+    fn test_rightmost_point() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "", ctx);
+                assert_eq!(buffer.rightmost_point().row, 0);
+                buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
+                assert_eq!(buffer.rightmost_point().row, 0);
+                buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
+                assert_eq!(buffer.rightmost_point().row, 2);
+                buffer.edit(vec![18..18], "\npqrs", None).unwrap();
+                assert_eq!(buffer.rightmost_point().row, 2);
+                buffer.edit(vec![10..12], "", None).unwrap();
+                assert_eq!(buffer.rightmost_point().row, 0);
+                buffer.edit(vec![24..24], "tuv", None).unwrap();
+                assert_eq!(buffer.rightmost_point().row, 4);
+                buffer
+            });
+        });
     }
 
     #[test]
     fn test_text_summary_for_range() {
-        let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz");
-        let text = Text::from(buffer.text());
-
-        assert_eq!(
-            buffer.text_summary_for_range(1..3),
-            text.slice(1..3).summary()
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(1..12),
-            text.slice(1..12).summary()
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(0..20),
-            text.slice(0..20).summary()
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(0..22),
-            text.slice(0..22).summary()
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(7..22),
-            text.slice(7..22).summary()
-        );
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx);
+                let text = Text::from(buffer.text());
+                assert_eq!(
+                    buffer.text_summary_for_range(1..3),
+                    text.slice(1..3).summary()
+                );
+                assert_eq!(
+                    buffer.text_summary_for_range(1..12),
+                    text.slice(1..12).summary()
+                );
+                assert_eq!(
+                    buffer.text_summary_for_range(0..20),
+                    text.slice(0..20).summary()
+                );
+                assert_eq!(
+                    buffer.text_summary_for_range(0..22),
+                    text.slice(0..22).summary()
+                );
+                assert_eq!(
+                    buffer.text_summary_for_range(7..22),
+                    text.slice(7..22).summary()
+                );
+                buffer
+            });
+        });
     }
 
     #[test]
-    fn test_chars_at() -> Result<()> {
-        let mut buffer = Buffer::new(0, "");
-        buffer.edit(vec![0..0], "abcd\nefgh\nij", None)?;
-        buffer.edit(vec![12..12], "kl\nmno", None)?;
-        buffer.edit(vec![18..18], "\npqrs", None)?;
-        buffer.edit(vec![18..21], "\nPQ", None)?;
+    fn test_chars_at() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "", ctx);
+                buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap();
+                buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
+                buffer.edit(vec![18..18], "\npqrs", None).unwrap();
+                buffer.edit(vec![18..21], "\nPQ", None).unwrap();
 
-        let chars = buffer.chars_at(Point::new(0, 0))?;
-        assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
+                let chars = buffer.chars_at(Point::new(0, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
 
-        let chars = buffer.chars_at(Point::new(1, 0))?;
-        assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
+                let chars = buffer.chars_at(Point::new(1, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
 
-        let chars = buffer.chars_at(Point::new(2, 0))?;
-        assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
+                let chars = buffer.chars_at(Point::new(2, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
 
-        let chars = buffer.chars_at(Point::new(3, 0))?;
-        assert_eq!(chars.collect::<String>(), "mno\nPQrs");
+                let chars = buffer.chars_at(Point::new(3, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "mno\nPQrs");
 
-        let chars = buffer.chars_at(Point::new(4, 0))?;
-        assert_eq!(chars.collect::<String>(), "PQrs");
+                let chars = buffer.chars_at(Point::new(4, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "PQrs");
 
-        // Regression test:
-        let mut buffer = Buffer::new(0, "");
-        buffer.edit(vec![0..0], "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n", None)?;
-        buffer.edit(vec![60..60], "\n", None)?;
+                // Regression test:
+                let mut buffer = Buffer::new(0, "", ctx);
+                buffer.edit(vec![0..0], "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n", None).unwrap();
+                buffer.edit(vec![60..60], "\n", None).unwrap();
 
-        let chars = buffer.chars_at(Point::new(6, 0))?;
-        assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
+                let chars = buffer.chars_at(Point::new(6, 0)).unwrap();
+                assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
 
-        Ok(())
+                buffer
+            });
+        });
     }
 
     // #[test]
@@ -2706,177 +2750,202 @@ mod tests {
     }
 
     #[test]
-    fn test_anchors() -> Result<()> {
-        let mut buffer = Buffer::new(0, "");
-        buffer.edit(vec![0..0], "abc", None)?;
-        let left_anchor = buffer.anchor_before(2).unwrap();
-        let right_anchor = buffer.anchor_after(2).unwrap();
-
-        buffer.edit(vec![1..1], "def\n", None)?;
-        assert_eq!(buffer.text(), "adef\nbc");
-        assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 6);
-        assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 6);
-        assert_eq!(
-            left_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 }
-        );
-        assert_eq!(
-            right_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 }
-        );
+    fn test_anchors() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "", ctx);
+                buffer.edit(vec![0..0], "abc", None).unwrap();
+                let left_anchor = buffer.anchor_before(2).unwrap();
+                let right_anchor = buffer.anchor_after(2).unwrap();
+
+                buffer.edit(vec![1..1], "def\n", None).unwrap();
+                assert_eq!(buffer.text(), "adef\nbc");
+                assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 6);
+                assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 6);
+                assert_eq!(
+                    left_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 }
+                );
+                assert_eq!(
+                    right_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 }
+                );
 
-        buffer.edit(vec![2..3], "", None)?;
-        assert_eq!(buffer.text(), "adf\nbc");
-        assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
-        assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 5);
-        assert_eq!(
-            left_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 }
-        );
-        assert_eq!(
-            right_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 }
-        );
+                buffer.edit(vec![2..3], "", None).unwrap();
+                assert_eq!(buffer.text(), "adf\nbc");
+                assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+                assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 5);
+                assert_eq!(
+                    left_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 }
+                );
+                assert_eq!(
+                    right_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 }
+                );
 
-        buffer.edit(vec![5..5], "ghi\n", None)?;
-        assert_eq!(buffer.text(), "adf\nbghi\nc");
-        assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
-        assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 9);
-        assert_eq!(
-            left_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 }
-        );
-        assert_eq!(
-            right_anchor.to_point(&buffer).unwrap(),
-            Point { row: 2, column: 0 }
-        );
+                buffer.edit(vec![5..5], "ghi\n", None).unwrap();
+                assert_eq!(buffer.text(), "adf\nbghi\nc");
+                assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+                assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 9);
+                assert_eq!(
+                    left_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 }
+                );
+                assert_eq!(
+                    right_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 2, column: 0 }
+                );
 
-        buffer.edit(vec![7..9], "", None)?;
-        assert_eq!(buffer.text(), "adf\nbghc");
-        assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
-        assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 7);
-        assert_eq!(
-            left_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 1 },
-        );
-        assert_eq!(
-            right_anchor.to_point(&buffer).unwrap(),
-            Point { row: 1, column: 3 }
-        );
+                buffer.edit(vec![7..9], "", None).unwrap();
+                assert_eq!(buffer.text(), "adf\nbghc");
+                assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+                assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 7);
+                assert_eq!(
+                    left_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 1 },
+                );
+                assert_eq!(
+                    right_anchor.to_point(&buffer).unwrap(),
+                    Point { row: 1, column: 3 }
+                );
 
-        // Ensure anchoring to a point is equivalent to anchoring to an offset.
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 0 })?,
-            buffer.anchor_before(0)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 1 })?,
-            buffer.anchor_before(1)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 2 })?,
-            buffer.anchor_before(2)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 3 })?,
-            buffer.anchor_before(3)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 0 })?,
-            buffer.anchor_before(4)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 1 })?,
-            buffer.anchor_before(5)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 2 })?,
-            buffer.anchor_before(6)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 3 })?,
-            buffer.anchor_before(7)?
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 4 })?,
-            buffer.anchor_before(8)?
-        );
+                // Ensure anchoring to a point is equivalent to anchoring to an offset.
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 0, column: 0 }).unwrap(),
+                    buffer.anchor_before(0).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 0, column: 1 }).unwrap(),
+                    buffer.anchor_before(1).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 0, column: 2 }).unwrap(),
+                    buffer.anchor_before(2).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 0, column: 3 }).unwrap(),
+                    buffer.anchor_before(3).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 1, column: 0 }).unwrap(),
+                    buffer.anchor_before(4).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 1, column: 1 }).unwrap(),
+                    buffer.anchor_before(5).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 1, column: 2 }).unwrap(),
+                    buffer.anchor_before(6).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 1, column: 3 }).unwrap(),
+                    buffer.anchor_before(7).unwrap()
+                );
+                assert_eq!(
+                    buffer.anchor_before(Point { row: 1, column: 4 }).unwrap(),
+                    buffer.anchor_before(8).unwrap()
+                );
 
-        // Comparison between anchors.
-        let anchor_at_offset_0 = buffer.anchor_before(0).unwrap();
-        let anchor_at_offset_1 = buffer.anchor_before(1).unwrap();
-        let anchor_at_offset_2 = buffer.anchor_before(2).unwrap();
+                // Comparison between anchors.
+                let anchor_at_offset_0 = buffer.anchor_before(0).unwrap();
+                let anchor_at_offset_1 = buffer.anchor_before(1).unwrap();
+                let anchor_at_offset_2 = buffer.anchor_before(2).unwrap();
 
-        assert_eq!(
-            anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer)?,
-            Ordering::Equal
-        );
-        assert_eq!(
-            anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer)?,
-            Ordering::Equal
-        );
-        assert_eq!(
-            anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer)?,
-            Ordering::Equal
-        );
+                assert_eq!(
+                    anchor_at_offset_0
+                        .cmp(&anchor_at_offset_0, &buffer)
+                        .unwrap(),
+                    Ordering::Equal
+                );
+                assert_eq!(
+                    anchor_at_offset_1
+                        .cmp(&anchor_at_offset_1, &buffer)
+                        .unwrap(),
+                    Ordering::Equal
+                );
+                assert_eq!(
+                    anchor_at_offset_2
+                        .cmp(&anchor_at_offset_2, &buffer)
+                        .unwrap(),
+                    Ordering::Equal
+                );
 
-        assert_eq!(
-            anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer)?,
-            Ordering::Less
-        );
-        assert_eq!(
-            anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer)?,
-            Ordering::Less
-        );
-        assert_eq!(
-            anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer)?,
-            Ordering::Less
-        );
+                assert_eq!(
+                    anchor_at_offset_0
+                        .cmp(&anchor_at_offset_1, &buffer)
+                        .unwrap(),
+                    Ordering::Less
+                );
+                assert_eq!(
+                    anchor_at_offset_1
+                        .cmp(&anchor_at_offset_2, &buffer)
+                        .unwrap(),
+                    Ordering::Less
+                );
+                assert_eq!(
+                    anchor_at_offset_0
+                        .cmp(&anchor_at_offset_2, &buffer)
+                        .unwrap(),
+                    Ordering::Less
+                );
 
-        assert_eq!(
-            anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer)?,
-            Ordering::Greater
-        );
-        assert_eq!(
-            anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer)?,
-            Ordering::Greater
-        );
-        assert_eq!(
-            anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer)?,
-            Ordering::Greater
-        );
-        Ok(())
+                assert_eq!(
+                    anchor_at_offset_1
+                        .cmp(&anchor_at_offset_0, &buffer)
+                        .unwrap(),
+                    Ordering::Greater
+                );
+                assert_eq!(
+                    anchor_at_offset_2
+                        .cmp(&anchor_at_offset_1, &buffer)
+                        .unwrap(),
+                    Ordering::Greater
+                );
+                assert_eq!(
+                    anchor_at_offset_2
+                        .cmp(&anchor_at_offset_0, &buffer)
+                        .unwrap(),
+                    Ordering::Greater
+                );
+                buffer
+            });
+        });
     }
 
     #[test]
-    fn test_anchors_at_start_and_end() -> Result<()> {
-        let mut buffer = Buffer::new(0, "");
-        let before_start_anchor = buffer.anchor_before(0).unwrap();
-        let after_end_anchor = buffer.anchor_after(0).unwrap();
-
-        buffer.edit(vec![0..0], "abc", None)?;
-        assert_eq!(buffer.text(), "abc");
-        assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
-        assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 3);
-
-        let after_start_anchor = buffer.anchor_after(0).unwrap();
-        let before_end_anchor = buffer.anchor_before(3).unwrap();
-
-        buffer.edit(vec![3..3], "def", None)?;
-        buffer.edit(vec![0..0], "ghi", None)?;
-        assert_eq!(buffer.text(), "ghiabcdef");
-        assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
-        assert_eq!(after_start_anchor.to_offset(&buffer).unwrap(), 3);
-        assert_eq!(before_end_anchor.to_offset(&buffer).unwrap(), 6);
-        assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
-
-        Ok(())
+    fn test_anchors_at_start_and_end() {
+        App::test((), |ctx| {
+            ctx.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "", ctx);
+                let before_start_anchor = buffer.anchor_before(0).unwrap();
+                let after_end_anchor = buffer.anchor_after(0).unwrap();
+
+                buffer.edit(vec![0..0], "abc", None).unwrap();
+                assert_eq!(buffer.text(), "abc");
+                assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
+                assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 3);
+
+                let after_start_anchor = buffer.anchor_after(0).unwrap();
+                let before_end_anchor = buffer.anchor_before(3).unwrap();
+
+                buffer.edit(vec![3..3], "def", None).unwrap();
+                buffer.edit(vec![0..0], "ghi", None).unwrap();
+                assert_eq!(buffer.text(), "ghiabcdef");
+                assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
+                assert_eq!(after_start_anchor.to_offset(&buffer).unwrap(), 3);
+                assert_eq!(before_end_anchor.to_offset(&buffer).unwrap(), 6);
+                assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
+                buffer
+            });
+        });
     }
 
     #[test]
-    fn test_is_modified() -> Result<()> {
+    fn test_is_modified() {
         App::test((), |app| {
-            let model = app.add_model(|_| Buffer::new(0, "abc"));
+            let model = app.add_model(|ctx| Buffer::new(0, "abc", ctx));
             let events = Rc::new(RefCell::new(Vec::new()));
 
             // initially, the buffer isn't dirty.
@@ -2958,94 +3027,113 @@ mod tests {
                 );
             });
         });
-        Ok(())
     }
 
     #[test]
-    fn test_undo_redo() -> Result<()> {
-        let mut buffer = Buffer::new(0, "1234");
-
-        let edit1 = buffer.edit(vec![1..1], "abx", None)?;
-        let edit2 = buffer.edit(vec![3..4], "yzef", None)?;
-        let edit3 = buffer.edit(vec![3..5], "cd", None)?;
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1cdef234");
-        buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abcdx234");
-        buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abx234");
-        buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abyzef234");
-        buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1abyzef234");
-        buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1yzef234");
-        buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
-        assert_eq!(buffer.text(), "1234");
-
-        Ok(())
+    fn test_undo_redo() {
+        App::test((), |app| {
+            app.add_model(|ctx| {
+                let mut buffer = Buffer::new(0, "1234", ctx);
+
+                let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap();
+                let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap();
+                let edit3 = buffer.edit(vec![3..5], "cd", None).unwrap();
+                assert_eq!(buffer.text(), "1abcdef234");
+
+                buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1cdef234");
+                buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abcdef234");
+
+                buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abcdx234");
+                buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abx234");
+                buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abyzef234");
+                buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abcdef234");
+
+                buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1abyzef234");
+                buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1yzef234");
+                buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+                assert_eq!(buffer.text(), "1234");
+
+                buffer
+            });
+        });
     }
 
     #[test]
-    fn test_history() -> Result<()> {
-        let mut now = Instant::now();
-        let mut buffer = Buffer::new(0, "123456");
-
-        let (set_id, _) =
-            buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4])?, None);
-        buffer.start_transaction_at(Some(set_id), now)?;
-        buffer.edit(vec![2..4], "cd", None)?;
-        buffer.end_transaction_at(Some(set_id), now, None)?;
-        assert_eq!(buffer.text(), "12cd56");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![4..4]);
-
-        buffer.start_transaction_at(Some(set_id), now)?;
-        buffer.update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3])?, None)?;
-        buffer.edit(vec![4..5], "e", None)?;
-        buffer.end_transaction_at(Some(set_id), now, None)?;
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
-
-        now += UNDO_GROUP_INTERVAL + Duration::from_millis(1);
-        buffer.start_transaction_at(Some(set_id), now)?;
-        buffer.update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2])?, None)?;
-        buffer.edit(vec![0..1], "a", None)?;
-        buffer.edit(vec![1..1], "b", None)?;
-        buffer.end_transaction_at(Some(set_id), now, None)?;
-        assert_eq!(buffer.text(), "ab2cde6");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![3..3]);
-
-        // Last transaction happened past the group interval, undo it on its
-        // own.
-        buffer.undo(None);
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
-
-        // First two transactions happened within the group interval, undo them
-        // together.
-        buffer.undo(None);
-        assert_eq!(buffer.text(), "123456");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![4..4]);
-
-        // Redo the first two transactions together.
-        buffer.redo(None);
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
-
-        // Redo the last transaction on its own.
-        buffer.redo(None);
-        assert_eq!(buffer.text(), "ab2cde6");
-        assert_eq!(buffer.selection_ranges(set_id)?, vec![3..3]);
-
-        Ok(())
+    fn test_history() {
+        App::test((), |app| {
+            app.add_model(|ctx| {
+                let mut now = Instant::now();
+                let mut buffer = Buffer::new(0, "123456", ctx);
+
+                let (set_id, _) = buffer
+                    .add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
+                buffer.start_transaction_at(Some(set_id), now).unwrap();
+                buffer.edit(vec![2..4], "cd", None).unwrap();
+                buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+                assert_eq!(buffer.text(), "12cd56");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+                buffer.start_transaction_at(Some(set_id), now).unwrap();
+                buffer
+                    .update_selection_set(
+                        set_id,
+                        buffer.selections_from_ranges(vec![1..3]).unwrap(),
+                        None,
+                    )
+                    .unwrap();
+                buffer.edit(vec![4..5], "e", None).unwrap();
+                buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+                assert_eq!(buffer.text(), "12cde6");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+                now += UNDO_GROUP_INTERVAL + Duration::from_millis(1);
+                buffer.start_transaction_at(Some(set_id), now).unwrap();
+                buffer
+                    .update_selection_set(
+                        set_id,
+                        buffer.selections_from_ranges(vec![2..2]).unwrap(),
+                        None,
+                    )
+                    .unwrap();
+                buffer.edit(vec![0..1], "a", None).unwrap();
+                buffer.edit(vec![1..1], "b", None).unwrap();
+                buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+                assert_eq!(buffer.text(), "ab2cde6");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+                // Last transaction happened past the group interval, undo it on its
+                // own.
+                buffer.undo(None);
+                assert_eq!(buffer.text(), "12cde6");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+                // First two transactions happened within the group interval, undo them
+                // together.
+                buffer.undo(None);
+                assert_eq!(buffer.text(), "123456");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+                // Redo the first two transactions together.
+                buffer.redo(None);
+                assert_eq!(buffer.text(), "12cde6");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+                // Redo the last transaction on its own.
+                buffer.redo(None);
+                assert_eq!(buffer.text(), "ab2cde6");
+                assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+                buffer
+            });
+        });
     }
 
     #[test]

zed/src/editor/buffer_view.rs 🔗

@@ -119,7 +119,7 @@ struct ClipboardSelection {
 
 impl BufferView {
     pub fn single_line(settings: watch::Receiver<Settings>, ctx: &mut ViewContext<Self>) -> Self {
-        let buffer = ctx.add_model(|_| Buffer::new(0, String::new()));
+        let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx));
         let mut view = Self::for_buffer(buffer, settings, ctx);
         view.single_line = true;
         view
@@ -1316,6 +1316,7 @@ impl BufferView {
             buffer::Event::Edited(_) => ctx.emit(Event::Edited),
             buffer::Event::Dirtied => ctx.emit(Event::Dirtied),
             buffer::Event::Saved => ctx.emit(Event::Saved),
+            buffer::Event::FileHandleChanged => ctx.emit(Event::FileHandleChanged),
         }
     }
 }
@@ -1326,6 +1327,7 @@ pub enum Event {
     Blurred,
     Dirtied,
     Saved,
+    FileHandleChanged,
 }
 
 impl Entity for BufferView {
@@ -1372,7 +1374,10 @@ impl workspace::ItemView for BufferView {
     }
 
     fn should_update_tab_on_event(event: &Self::Event) -> bool {
-        matches!(event, Event::Saved | Event::Dirtied)
+        matches!(
+            event,
+            Event::Saved | Event::Dirtied | Event::FileHandleChanged
+        )
     }
 
     fn title(&self, app: &AppContext) -> std::string::String {
@@ -1419,7 +1424,8 @@ mod tests {
     #[test]
     fn test_selection_with_mouse() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
+            let buffer =
+                app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let (_, buffer_view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@@ -1533,7 +1539,7 @@ mod tests {
             let layout_cache = TextLayoutCache::new(app.platform().fonts());
             let font_cache = app.font_cache().clone();
 
-            let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
+            let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
 
             let settings = settings::channel(&font_cache).unwrap().1;
             let (_, view) =
@@ -1550,7 +1556,7 @@ mod tests {
     #[test]
     fn test_fold() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| {
+            let buffer = app.add_model(|ctx| {
                 Buffer::new(
                     0,
                     "
@@ -1571,6 +1577,7 @@ mod tests {
                     }
                 "
                     .unindent(),
+                    ctx,
                 )
             });
             let settings = settings::channel(&app.font_cache()).unwrap().1;
@@ -1644,7 +1651,7 @@ mod tests {
     #[test]
     fn test_move_cursor() -> Result<()> {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
+            let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let (_, view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
@@ -1681,8 +1688,12 @@ mod tests {
     #[test]
     fn test_backspace() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| {
-                Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
+            let buffer = app.add_model(|ctx| {
+                Buffer::new(
+                    0,
+                    "one two three\nfour five six\nseven eight nine\nten\n",
+                    ctx,
+                )
             });
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let (_, view) =
@@ -1714,7 +1725,7 @@ mod tests {
     #[test]
     fn test_clipboard() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six "));
+            let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx));
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let view = app
                 .add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx))

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

@@ -471,7 +471,7 @@ mod tests {
     #[test]
     fn test_basic_folds() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+            let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
             let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 
             map.fold(
@@ -522,7 +522,7 @@ mod tests {
     #[test]
     fn test_overlapping_folds() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+            let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
             let mut map = FoldMap::new(buffer.clone(), app.as_ref());
             map.fold(
                 vec![
@@ -541,7 +541,7 @@ mod tests {
     #[test]
     fn test_merging_folds_via_edit() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+            let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
             let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 
             map.fold(
@@ -589,10 +589,10 @@ mod tests {
             let mut rng = StdRng::seed_from_u64(seed);
 
             App::test((), |app| {
-                let buffer = app.add_model(|_| {
+                let buffer = app.add_model(|ctx| {
                     let len = rng.gen_range(0..10);
                     let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
-                    Buffer::new(0, text)
+                    Buffer::new(0, text, ctx)
                 });
                 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 
@@ -664,7 +664,7 @@ mod tests {
     fn test_buffer_rows() {
         App::test((), |app| {
             let text = sample_text(6, 6) + "\n";
-            let buffer = app.add_model(|_| Buffer::new(0, text));
+            let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
 
             let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 

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

@@ -298,7 +298,7 @@ mod tests {
     fn test_chars_at() {
         App::test((), |app| {
             let text = sample_text(6, 6);
-            let buffer = app.add_model(|_| Buffer::new(0, text));
+            let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
             let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
             buffer
                 .update(app, |buffer, ctx| {
@@ -365,7 +365,7 @@ mod tests {
     #[test]
     fn test_max_point() {
         App::test((), |app| {
-            let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
+            let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
             let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
             assert_eq!(
                 map.read(app).max_point(app.as_ref()),

zed/src/workspace/workspace.rs 🔗

@@ -1,6 +1,6 @@
 use super::{ItemView, ItemViewHandle};
 use crate::{
-    editor::Buffer,
+    editor::{Buffer, History},
     settings::Settings,
     time::ReplicaId,
     watch,
@@ -174,15 +174,17 @@ impl Workspace {
         let replica_id = self.replica_id;
         let file = worktree.file(path.clone(), ctx.as_ref())?;
         let history = file.load_history(ctx.as_ref());
-        let buffer = async move { Ok(Buffer::from_history(replica_id, file, history.await?)) };
+        // let buffer = async move { Ok(Buffer::from_history(replica_id, file, history.await?)) };
 
         let (mut tx, rx) = watch::channel(None);
         self.items.insert(item_key, OpenedItem::Loading(rx));
         ctx.spawn(
-            buffer,
-            move |me, buffer: anyhow::Result<Buffer>, ctx| match buffer {
-                Ok(buffer) => {
-                    let handle = Box::new(ctx.add_model(|_| buffer)) as Box<dyn ItemHandle>;
+            history,
+            move |me, history: anyhow::Result<History>, ctx| match history {
+                Ok(history) => {
+                    let handle = Box::new(
+                        ctx.add_model(|ctx| Buffer::from_history(replica_id, file, history, ctx)),
+                    ) as Box<dyn ItemHandle>;
                     me.items
                         .insert(item_key, OpenedItem::Loaded(handle.clone()));
                     ctx.spawn(

zed/src/worktree.rs 🔗

@@ -59,7 +59,7 @@ pub struct FileHandle {
     state: Arc<Mutex<FileHandleState>>,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 struct FileHandleState {
     path: Arc<Path>,
     is_deleted: bool,
@@ -403,6 +403,32 @@ impl FileHandle {
     pub fn entry_id(&self) -> (usize, Arc<Path>) {
         (self.worktree.id(), self.path())
     }
+
+    pub fn observe_from_model<T: Entity>(
+        &self,
+        ctx: &mut ModelContext<T>,
+        mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext<T>) + 'static,
+    ) {
+        let mut prev_state = self.state.lock().clone();
+        let cur_state = Arc::downgrade(&self.state);
+        ctx.observe(&self.worktree, move |observer, worktree, ctx| {
+            if let Some(cur_state) = cur_state.upgrade() {
+                let cur_state_unlocked = cur_state.lock();
+                if *cur_state_unlocked != prev_state {
+                    prev_state = cur_state_unlocked.clone();
+                    drop(cur_state_unlocked);
+                    callback(
+                        observer,
+                        FileHandle {
+                            worktree,
+                            state: cur_state,
+                        },
+                        ctx,
+                    );
+                }
+            }
+        });
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -818,7 +844,12 @@ impl BackgroundScanner {
                             handles.retain(|handle_path, handle_state| {
                                 if let Ok(path_suffix) = handle_path.strip_prefix(&old_path) {
                                     let new_handle_path: Arc<Path> =
-                                        new_path.join(path_suffix).into();
+                                        if path_suffix.file_name().is_some() {
+                                            new_path.join(path_suffix)
+                                        } else {
+                                            new_path.to_path_buf()
+                                        }
+                                        .into();
                                     if let Some(handle_state) = Weak::upgrade(&handle_state) {
                                         handle_state.lock().path = new_handle_path.clone();
                                         updated_handles
@@ -1266,20 +1297,24 @@ mod tests {
             app.read(|ctx| tree.read(ctx).scan_complete()).await;
             app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
 
-            let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
+            let buffer =
+                app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
 
             let path = tree.update(&mut app, |tree, ctx| {
                 let path = tree.files(0).next().unwrap().path().clone();
                 assert_eq!(path.file_name().unwrap(), "file1");
-                smol::block_on(tree.save(&path, buffer.snapshot(), ctx.as_ref())).unwrap();
+                smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref()))
+                    .unwrap();
                 path
             });
 
-            let loaded_history = app
+            let history = app
                 .read(|ctx| tree.read(ctx).load_history(&path, ctx))
                 .await
                 .unwrap();
-            assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
+            app.read(|ctx| {
+                assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
+            });
         });
     }
 
@@ -1335,7 +1370,8 @@ mod tests {
                         "d/file4"
                     ]
                 );
-                assert_eq!(file2.path().as_ref(), Path::new("a/file2.new"));
+
+                assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
                 assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
                 assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
                 assert!(!file2.is_deleted());