diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 58ed8b08872e07fe31911ba9494b841bf4233633..0888e3347edc8ea59e09e8f99f6315147db6e248 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -597,6 +597,15 @@ impl TestAppContext { pub fn leak_detector(&self) -> Arc> { self.cx.borrow().leak_detector() } + + #[cfg(any(test, feature = "test-support"))] + pub fn assert_dropped(&self, handle: impl WeakHandle) { + self.cx + .borrow() + .leak_detector() + .lock() + .assert_dropped(handle.id()) + } } impl AsyncAppContext { @@ -3302,6 +3311,10 @@ pub trait Handle { Self: Sized; } +pub trait WeakHandle { + fn id(&self) -> usize; +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum EntityLocation { Model(usize), @@ -3576,6 +3589,12 @@ pub struct WeakModelHandle { model_type: PhantomData, } +impl WeakHandle for WeakModelHandle { + fn id(&self) -> usize { + self.model_id + } +} + unsafe impl Send for WeakModelHandle {} unsafe impl Sync for WeakModelHandle {} @@ -4145,6 +4164,12 @@ pub struct WeakViewHandle { view_type: PhantomData, } +impl WeakHandle for WeakViewHandle { + fn id(&self) -> usize { + self.view_id + } +} + impl WeakViewHandle { fn new(window_id: usize, view_id: usize) -> Self { Self { @@ -4471,11 +4496,36 @@ impl LeakDetector { } } + pub fn assert_dropped(&mut self, entity_id: usize) { + if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) { + for trace in backtraces.values_mut() { + if let Some(trace) = trace { + trace.resolve(); + eprintln!("{:?}", crate::util::CwdBacktrace(trace)); + } + } + + let hint = if *LEAK_BACKTRACE { + "" + } else { + " – set LEAK_BACKTRACE=1 for more information" + }; + + panic!( + "{} handles to {} {} still exist{}", + backtraces.len(), + type_name.unwrap_or("entity"), + entity_id, + hint + ); + } + } + pub fn detect(&mut self) { let mut found_leaks = false; for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() { eprintln!( - "leaked {} handles to {:?} {}", + "leaked {} handles to {} {}", backtraces.len(), type_name.unwrap_or("entity"), id diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index baf1c2e405650cdddfe891a773177820d50dc3f4..fb13ef2fd0e89dda0e38e00f0c39cecce0c4abd1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -580,6 +580,7 @@ impl Pane { let item = pane.items.remove(item_ix); if pane.items.is_empty() { item.deactivated(cx); + pane.update_toolbar(cx); cx.emit(Event::Remove); } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 843e56fd0e21f0c4cd6dc7b8c6175419eadef281..188a48d86df19381323c846c738984ee2dc74940 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -725,32 +725,47 @@ mod tests { .update(cx, |w, cx| w.open_path(file1.clone(), cx)) .await .unwrap(); - cx.read(|cx| { - assert_eq!( - pane_1.read(cx).active_item().unwrap().project_path(cx), - Some(file1.clone()) - ); + + let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| { + let editor = pane_1.active_item().unwrap().downcast::().unwrap(); + assert_eq!(editor.project_path(cx), Some(file1.clone())); + let buffer = editor.update(cx, |editor, cx| { + editor.insert("dirt", cx); + editor.buffer().downgrade() + }); + (editor.downgrade(), buffer) }); cx.dispatch_action(window_id, pane::Split(SplitDirection::Right)); - cx.update(|cx| { + let editor_2 = cx.update(|cx| { let pane_2 = workspace.read(cx).active_pane().clone(); assert_ne!(pane_1, pane_2); let pane2_item = pane_2.read(cx).active_item().unwrap(); assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone())); - cx.dispatch_action( - window_id, - vec![workspace.id(), pane_2.id()], - &workspace::CloseActiveItem, - ); + pane2_item.downcast::().unwrap().downgrade() }); + cx.dispatch_action(window_id, workspace::CloseActiveItem); + cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.panes().len(), 1); assert_eq!(workspace.active_pane(), &pane_1); }); + + cx.dispatch_action(window_id, workspace::CloseActiveItem); + cx.foreground().run_until_parked(); + cx.simulate_prompt_answer(window_id, 1); + cx.foreground().run_until_parked(); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.active_item(cx).is_none()); + }); + + cx.assert_dropped(editor_1); + cx.assert_dropped(editor_2); + cx.assert_dropped(buffer); } #[gpui::test]