diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 863cb94f27..178b8f9b69 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -1620,7 +1620,7 @@ impl<'a, T: Entity> ModelContext<'a, T> { }); } - fn handle(&self) -> ModelHandle { + pub fn handle(&self) -> ModelHandle { ModelHandle::new(self.model_id, &self.app.ctx.ref_counts) } diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 6d35e9bd42..d42c3460cf 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -377,7 +377,12 @@ impl Buffer { ctx: &mut ModelContext, ) -> Self { if let Some(file) = file.as_ref() { - file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); + file.observe_from_model(ctx, |this, file, ctx| { + if this.version == this.saved_version && file.is_deleted() { + ctx.emit(Event::Dirtied); + } + ctx.emit(Event::FileHandleChanged); + }); } let mut insertion_splits = HashMap::default(); @@ -2989,19 +2994,21 @@ mod tests { App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "file1": "", + "file2": "", + "file3": "", })); let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx)); app.read(|ctx| tree.read(ctx).scan_complete()).await; - let file = app.read(|ctx| tree.file("file1", ctx)); - let model = app.add_model(|ctx| { - Buffer::from_history(0, History::new("abc".into()), Some(file), ctx) + let file1 = app.read(|ctx| tree.file("file1", ctx)); + let buffer1 = app.add_model(|ctx| { + Buffer::from_history(0, History::new("abc".into()), Some(file1), ctx) }); let events = Rc::new(RefCell::new(Vec::new())); // initially, the buffer isn't dirty. - model.update(&mut app, |buffer, ctx| { - ctx.subscribe(&model, { + buffer1.update(&mut app, |buffer, ctx| { + ctx.subscribe(&buffer1, { let events = events.clone(); move |_, event, _| events.borrow_mut().push(event.clone()) }); @@ -3013,7 +3020,7 @@ mod tests { }); // after the first edit, the buffer is dirty, and emits a dirtied event. - model.update(&mut app, |buffer, ctx| { + buffer1.update(&mut app, |buffer, ctx| { assert!(buffer.text() == "ac"); assert!(buffer.is_dirty()); assert_eq!(*events.borrow(), &[Event::Edited, Event::Dirtied]); @@ -3023,7 +3030,7 @@ mod tests { }); // after saving, the buffer is not dirty, and emits a saved event. - model.update(&mut app, |buffer, ctx| { + buffer1.update(&mut app, |buffer, ctx| { assert!(!buffer.is_dirty()); assert_eq!(*events.borrow(), &[Event::Saved]); events.borrow_mut().clear(); @@ -3033,7 +3040,7 @@ mod tests { }); // after editing again, the buffer is dirty, and emits another dirty event. - model.update(&mut app, |buffer, ctx| { + buffer1.update(&mut app, |buffer, ctx| { assert!(buffer.text() == "aBDc"); assert!(buffer.is_dirty()); assert_eq!( @@ -3051,22 +3058,50 @@ mod tests { assert_eq!(*events.borrow(), &[Event::Edited]); - // When the file is deleted, the buffer is considered dirty. - model.update(&mut app, |buffer, ctx| { - buffer.did_save(buffer.version(), None, ctx); - assert!(!buffer.is_dirty()); + // When a file is deleted, the buffer is considered dirty. + let events = Rc::new(RefCell::new(Vec::new())); + let file2 = app.read(|ctx| tree.file("file2", ctx)); + let buffer2 = app.add_model(|ctx: &mut ModelContext| { + ctx.subscribe(&ctx.handle(), { + let events = events.clone(); + move |_, event, _| events.borrow_mut().push(event.clone()) + }); + + Buffer::from_history(0, History::new("abc".into()), Some(file2), ctx) }); - events.borrow_mut().clear(); tree.flush_fs_events(&app).await; - fs::remove_file(dir.path().join("file1")).unwrap(); + fs::remove_file(dir.path().join("file2")).unwrap(); tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx)) .await; + assert_eq!( + *events.borrow(), + &[Event::Dirtied, Event::FileHandleChanged] + ); + app.read(|ctx| assert!(buffer2.read(ctx).is_dirty())); - model.update(&mut app, |buffer, _| { - assert_eq!(*events.borrow(), &[Event::FileHandleChanged]); - assert!(buffer.is_dirty()); + // When a file is already dirty when deleted, we don't emit a Dirtied event. + let events = Rc::new(RefCell::new(Vec::new())); + let file3 = app.read(|ctx| tree.file("file3", ctx)); + let buffer3 = app.add_model(|ctx: &mut ModelContext| { + ctx.subscribe(&ctx.handle(), { + let events = events.clone(); + move |_, event, _| events.borrow_mut().push(event.clone()) + }); + + Buffer::from_history(0, History::new("abc".into()), Some(file3), ctx) }); + + tree.flush_fs_events(&app).await; + buffer3.update(&mut app, |buffer, ctx| { + buffer.edit(Some(0..0), "x", Some(ctx)).unwrap(); + }); + events.borrow_mut().clear(); + fs::remove_file(dir.path().join("file3")).unwrap(); + tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx)) + .await; + assert_eq!(*events.borrow(), &[Event::FileHandleChanged]); + app.read(|ctx| assert!(buffer3.read(ctx).is_dirty())); }); }