From b695c42e1152ad9019b2c6bfcaf21834fe00b386 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 1 Aug 2023 22:28:04 -0600 Subject: [PATCH 1/9] WIP: Return WindowHandle from AppContext::add_window --- crates/gpui/src/app.rs | 352 ++++++++++++++++-------- crates/gpui/src/app/ref_counts.rs | 23 ++ crates/gpui/src/app/test_app_context.rs | 11 +- 3 files changed, 268 insertions(+), 118 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index da601ba351..b2d732d170 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -130,8 +130,12 @@ pub trait BorrowAppContext { } pub trait BorrowWindowContext { - fn read_with T>(&self, window_id: usize, f: F) -> T; - fn update T>(&mut self, window_id: usize, f: F) -> T; + fn read_with(&self, window_id: usize, f: F) -> T + where + F: FnOnce(&WindowContext) -> T; + fn update(&mut self, window_id: usize, f: F) -> T + where + F: FnOnce(&mut WindowContext) -> T; } #[derive(Clone)] @@ -402,7 +406,7 @@ impl AsyncAppContext { &mut self, window_options: WindowOptions, build_root_view: F, - ) -> (usize, ViewHandle) + ) -> WindowHandle where T: View, F: FnOnce(&mut ViewContext) -> T, @@ -1300,7 +1304,7 @@ impl AppContext { &mut self, window_options: WindowOptions, build_root_view: F, - ) -> (usize, ViewHandle) + ) -> WindowHandle where V: View, F: FnOnce(&mut ViewContext) -> V, @@ -1311,9 +1315,8 @@ impl AppContext { this.platform .open_window(window_id, window_options, this.foreground.clone()); let window = this.build_window(window_id, platform_window, build_root_view); - let root_view = window.root_view().clone().downcast::().unwrap(); this.windows.insert(window_id, window); - (window_id, root_view) + WindowHandle::new(window_id, this.ref_counts.clone()) }) } @@ -3802,6 +3805,131 @@ impl Clone for WeakModelHandle { impl Copy for WeakModelHandle {} +pub struct WindowHandle { + any_handle: AnyWindowHandle, + view_type: PhantomData, +} + +impl WindowHandle { + fn id(&self) -> usize { + self.any_handle.id() + } + + fn new(window_id: usize, ref_counts: Arc>) -> Self { + WindowHandle { + any_handle: AnyWindowHandle::new::(window_id, ref_counts), + view_type: PhantomData, + } + } + + fn root(&self, cx: &impl BorrowAppContext) -> ViewHandle { + self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap()) + } + + pub fn read_with(&self, cx: &C, read: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&WindowContext) -> R, + { + cx.read_with(|cx| cx.read_window(self.id(), read).unwrap()) + } + + pub fn update(&self, cx: &mut C, update: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut WindowContext) -> R, + { + cx.update(|cx| cx.update_window(self.id(), update).unwrap()) + } + + pub fn update_root(&self, cx: &mut C, update: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut V, &mut ViewContext) -> R, + { + let window_id = self.id(); + cx.update(|cx| { + cx.update_window(window_id, |cx| { + cx.root_view() + .clone() + .downcast::() + .unwrap() + .update(cx, update) + }) + .unwrap() + }) + } + + pub fn read_root<'a>(&self, cx: &'a AppContext) -> &'a V { + let root_view = cx + .read_window(self.id(), |cx| cx.root_view().clone().downcast().unwrap()) + .unwrap(); + root_view.read(cx) + } + + pub fn read_root_with(&self, cx: &C, read: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&V, &ViewContext) -> R, + { + self.read_with(cx, |cx| { + cx.root_view() + .downcast_ref::() + .unwrap() + .read_with(cx, read) + }) + } + + pub fn add_view(&self, cx: &mut C, build_view: F) -> ViewHandle + where + C: BorrowAppContext, + U: View, + F: FnOnce(&mut ViewContext) -> U, + { + self.update(cx, |cx| cx.add_view(build_view)) + } +} + +pub struct AnyWindowHandle { + window_id: usize, + root_view_type: TypeId, + ref_counts: Arc>, + + #[cfg(any(test, feature = "test-support"))] + handle_id: usize, +} + +impl AnyWindowHandle { + fn new(window_id: usize, ref_counts: Arc>) -> Self { + ref_counts.lock().inc_window(window_id); + + #[cfg(any(test, feature = "test-support"))] + let handle_id = ref_counts + .lock() + .leak_detector + .lock() + .handle_created(None, window_id); + + Self { + window_id, + root_view_type: TypeId::of::(), + ref_counts, + #[cfg(any(test, feature = "test-support"))] + handle_id, + } + } + + pub fn id(&self) -> usize { + self.window_id + } +} + +impl Drop for AnyWindowHandle { + fn drop(&mut self) { + self.ref_counts.lock().dec_window(self.window_id) + } +} + #[repr(transparent)] pub struct ViewHandle { any_handle: AnyViewHandle, @@ -4684,11 +4812,11 @@ mod tests { } } - let (_, view) = cx.add_window(|_| View { render_count: 0 }); + let window = cx.add_window(|_| View { render_count: 0 }); let called_defer = Rc::new(AtomicBool::new(false)); let called_after_window_update = Rc::new(AtomicBool::new(false)); - view.update(cx, |this, cx| { + window.update_root(cx, |this, cx| { assert_eq!(this.render_count, 1); cx.defer({ let called_defer = called_defer.clone(); @@ -4712,7 +4840,7 @@ mod tests { assert!(called_defer.load(SeqCst)); assert!(called_after_window_update.load(SeqCst)); - assert_eq!(view.read_with(cx, |view, _| view.render_count), 3); + assert_eq!(window.read_root_with(cx, |view, _| view.render_count), 3); } #[crate::test(self)] @@ -4751,9 +4879,9 @@ mod tests { } } - let (window_id, _root_view) = cx.add_window(|cx| View::new(None, cx)); - let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx)); - let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx)); + let window = cx.add_window(|cx| View::new(None, cx)); + let handle_1 = window.add_view(cx, |cx| View::new(None, cx)); + let handle_2 = window.add_view(cx, |cx| View::new(Some(handle_1.clone()), cx)); assert_eq!(cx.read(|cx| cx.views.len()), 3); handle_1.update(cx, |view, cx| { @@ -4813,11 +4941,11 @@ mod tests { } let mouse_down_count = Arc::new(AtomicUsize::new(0)); - let (window_id, _) = cx.add_window(Default::default(), |_| View { + let window = cx.add_window(Default::default(), |_| View { mouse_down_count: mouse_down_count.clone(), }); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { // Ensure window's root element is in a valid lifecycle state. cx.dispatch_event( Event::MouseDown(MouseButtonEvent { @@ -4876,9 +5004,11 @@ mod tests { let model = cx.add_model(|_| Model { released: model_released.clone(), }); - let (window_id, view) = cx.add_window(Default::default(), |_| View { + let window = cx.add_window(Default::default(), |_| View { released: view_released.clone(), }); + let view = window.root(cx); + assert!(!model_released.get()); assert!(!view_released.get()); @@ -4900,7 +5030,7 @@ mod tests { assert!(model_release_observed.get()); drop(view); - cx.update_window(window_id, |cx| cx.remove_window()); + window.update(cx, |cx| cx.remove_window()); assert!(view_released.get()); assert!(view_release_observed.get()); } @@ -4913,8 +5043,9 @@ mod tests { type Event = String; } - let (window_id, handle_1) = cx.add_window(|_| TestView::default()); - let handle_2 = cx.add_view(window_id, |_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let handle_1 = window.root(cx); + let handle_2 = window.add_view(cx, |_| TestView::default()); let handle_3 = cx.add_model(|_| Model); handle_1.update(cx, |_, cx| { @@ -5140,9 +5271,9 @@ mod tests { type Event = (); } - let (window_id, _root_view) = cx.add_window(|_| TestView::default()); - let observing_view = cx.add_view(window_id, |_| TestView::default()); - let emitting_view = cx.add_view(window_id, |_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let observing_view = window.add_view(cx, |_| TestView::default()); + let emitting_view = window.add_view(cx, |_| TestView::default()); let observing_model = cx.add_model(|_| Model); let observed_model = cx.add_model(|_| Model); @@ -5165,7 +5296,7 @@ mod tests { #[crate::test(self)] fn test_view_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) { - let (_, view) = cx.add_window::(Default::default(), |cx| { + let window = cx.add_window::(Default::default(), |cx| { drop(cx.subscribe(&cx.handle(), { move |this, _, _, _| this.events.push("dropped before flush".into()) })); @@ -5181,7 +5312,7 @@ mod tests { TestView { events: Vec::new() } }); - assert_eq!(view.read(cx).events, ["before emit"]); + assert_eq!(window.read_root(cx).events, ["before emit"]); } #[crate::test(self)] @@ -5195,7 +5326,8 @@ mod tests { type Event = (); } - let (_, view) = cx.add_window(|_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let view = window.root(cx); let model = cx.add_model(|_| Model { state: "old-state".into(), }); @@ -5216,7 +5348,7 @@ mod tests { #[crate::test(self)] fn test_view_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) { - let (_, view) = cx.add_window::(Default::default(), |cx| { + let window = cx.add_window::(Default::default(), |cx| { drop(cx.observe(&cx.handle(), { move |this, _, _| this.events.push("dropped before flush".into()) })); @@ -5232,7 +5364,7 @@ mod tests { TestView { events: Vec::new() } }); - assert_eq!(view.read(cx).events, ["before notify"]); + assert_eq!(window.read_root(cx).events, ["before notify"]); } #[crate::test(self)] @@ -5243,7 +5375,8 @@ mod tests { } let model = cx.add_model(|_| Model); - let (_, view) = cx.add_window(|_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let view = window.root(cx); view.update(cx, |_, cx| { model.update(cx, |_, cx| cx.notify()); @@ -5267,8 +5400,8 @@ mod tests { type Event = (); } - let (window_id, _root_view) = cx.add_window(|_| TestView::default()); - let observing_view = cx.add_view(window_id, |_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let observing_view = window.add_view(cx, |_| TestView::default()); let observing_model = cx.add_model(|_| Model); let observed_model = cx.add_model(|_| Model); @@ -5390,9 +5523,9 @@ mod tests { } } - let (window_id, _root_view) = cx.add_window(|_| View); - let observing_view = cx.add_view(window_id, |_| View); - let observed_view = cx.add_view(window_id, |_| View); + let window = cx.add_window(|_| View); + let observing_view = window.add_view(cx, |_| View); + let observed_view = window.add_view(cx, |_| View); let observation_count = Rc::new(RefCell::new(0)); observing_view.update(cx, |_, cx| { @@ -5474,13 +5607,14 @@ mod tests { } let view_events: Arc>> = Default::default(); - let (window_id, view_1) = cx.add_window(|_| View { + let window = cx.add_window(|_| View { events: view_events.clone(), name: "view 1".to_string(), child: None, }); - let view_2 = cx - .update_window(window_id, |cx| { + let view_1 = window.root(cx); + let view_2 = window + .update(cx, |cx| { let view_2 = cx.add_view(|_| View { events: view_events.clone(), name: "view 2".to_string(), @@ -5731,40 +5865,34 @@ mod tests { }) .detach(); - let (window_id, view_1) = - cx.add_window(Default::default(), |_| ViewA { id: 1, child: None }); - let view_2 = cx - .update_window(window_id, |cx| { - let child = cx.add_view(|_| ViewB { id: 2, child: None }); - view_1.update(cx, |view, cx| { - view.child = Some(child.clone().into_any()); - cx.notify(); - }); - child - }) - .unwrap(); - let view_3 = cx - .update_window(window_id, |cx| { - let child = cx.add_view(|_| ViewA { id: 3, child: None }); - view_2.update(cx, |view, cx| { - view.child = Some(child.clone().into_any()); - cx.notify(); - }); - child - }) - .unwrap(); - let view_4 = cx - .update_window(window_id, |cx| { - let child = cx.add_view(|_| ViewB { id: 4, child: None }); - view_3.update(cx, |view, cx| { - view.child = Some(child.clone().into_any()); - cx.notify(); - }); - child - }) - .unwrap(); + let window = cx.add_window(Default::default(), |_| ViewA { id: 1, child: None }); + let view_1 = window.root(cx); + let view_2 = window.update(cx, |cx| { + let child = cx.add_view(|_| ViewB { id: 2, child: None }); + view_1.update(cx, |view, cx| { + view.child = Some(child.clone().into_any()); + cx.notify(); + }); + child + }); + let view_3 = window.update(cx, |cx| { + let child = cx.add_view(|_| ViewA { id: 3, child: None }); + view_2.update(cx, |view, cx| { + view.child = Some(child.clone().into_any()); + cx.notify(); + }); + child + }); + let view_4 = window.update(cx, |cx| { + let child = cx.add_view(|_| ViewB { id: 4, child: None }); + view_3.update(cx, |view, cx| { + view.child = Some(child.clone().into_any()); + cx.notify(); + }); + child + }); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string())) }); @@ -5786,31 +5914,27 @@ mod tests { // Remove view_1, which doesn't propagate the action - let (window_id, view_2) = - cx.add_window(Default::default(), |_| ViewB { id: 2, child: None }); - let view_3 = cx - .update_window(window_id, |cx| { - let child = cx.add_view(|_| ViewA { id: 3, child: None }); - view_2.update(cx, |view, cx| { - view.child = Some(child.clone().into_any()); - cx.notify(); - }); - child - }) - .unwrap(); - let view_4 = cx - .update_window(window_id, |cx| { - let child = cx.add_view(|_| ViewB { id: 4, child: None }); - view_3.update(cx, |view, cx| { - view.child = Some(child.clone().into_any()); - cx.notify(); - }); - child - }) - .unwrap(); + let window = cx.add_window(Default::default(), |_| ViewB { id: 2, child: None }); + let view_2 = window.root(cx); + let view_3 = window.update(cx, |cx| { + let child = cx.add_view(|_| ViewA { id: 3, child: None }); + view_2.update(cx, |view, cx| { + view.child = Some(child.clone().into_any()); + cx.notify(); + }); + child + }); + let view_4 = window.update(cx, |cx| { + let child = cx.add_view(|_| ViewB { id: 4, child: None }); + view_3.update(cx, |view, cx| { + view.child = Some(child.clone().into_any()); + cx.notify(); + }); + child + }); actions.borrow_mut().clear(); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string())) }); @@ -5887,7 +6011,7 @@ mod tests { view_3.keymap_context.add_identifier("b"); view_3.keymap_context.add_identifier("c"); - let (window_id, _view_1) = cx.add_window(Default::default(), |cx| { + let window = cx.add_window(Default::default(), |cx| { let view_2 = cx.add_view(|cx| { let view_3 = cx.add_view(|cx| { cx.focus_self(); @@ -6006,13 +6130,14 @@ mod tests { } } - let (window_id, view_1) = cx.add_window(|cx| { + let window = cx.add_window(|cx| { let view_2 = cx.add_view(|cx| { cx.focus_self(); View2 {} }); View1 { child: view_2 } }); + let view_1 = window.root(cx); let view_2 = view_1.read_with(cx, |view, _| view.child.clone()); cx.update(|cx| { @@ -6138,7 +6263,8 @@ mod tests { impl_actions!(test, [ActionWithArg]); - let (window_id, view) = cx.add_window(|_| View); + let window = cx.add_window(|_| View); + let view = window.root(cx); cx.update(|cx| { cx.add_global_action(|_: &ActionWithArg, _| {}); cx.add_bindings(vec![ @@ -6250,7 +6376,8 @@ mod tests { } } - let (_, view) = cx.add_window(|_| Counter(0)); + let window = cx.add_window(|_| Counter(0)); + let view = window.root(cx); let condition1 = view.condition(cx, |view, _| view.0 == 2); let condition2 = view.condition(cx, |view, _| view.0 == 3); @@ -6272,15 +6399,15 @@ mod tests { #[crate::test(self)] #[should_panic] async fn test_view_condition_timeout(cx: &mut TestAppContext) { - let (_, view) = cx.add_window(|_| TestView::default()); - view.condition(cx, |_, _| false).await; + let window = cx.add_window(|_| TestView::default()); + window.root(cx).condition(cx, |_, _| false).await; } #[crate::test(self)] #[should_panic(expected = "view dropped with pending condition")] async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) { - let (window_id, _root_view) = cx.add_window(|_| TestView::default()); - let view = cx.add_view(window_id, |_| TestView::default()); + let window = cx.add_window(|_| TestView::default()); + let view = window.add_view(cx, |_| TestView::default()); let condition = view.condition(cx, |_, _| false); cx.update(|_| drop(view)); @@ -6305,22 +6432,21 @@ mod tests { } } - let (window_id, root_view) = cx.add_window(Default::default(), |_| View(0)); - cx.update_window(window_id, |cx| { + let window = cx.add_window(Default::default(), |_| View(0)); + let root_view = window.root(cx); + window.update(cx, |cx| { assert_eq!( cx.window.rendered_views[&root_view.id()].name(), Some("render count: 0") ); }); - let view = cx - .update_window(window_id, |cx| { - cx.refresh_windows(); - cx.add_view(|_| View(0)) - }) - .unwrap(); + let view = window.update(cx, |cx| { + cx.refresh_windows(); + cx.add_view(|_| View(0)) + }); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { assert_eq!( cx.window.rendered_views[&root_view.id()].name(), Some("render count: 1") @@ -6333,7 +6459,7 @@ mod tests { cx.update(|cx| cx.refresh_windows()); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { assert_eq!( cx.window.rendered_views[&root_view.id()].name(), Some("render count: 2") @@ -6349,7 +6475,7 @@ mod tests { drop(view); }); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { assert_eq!( cx.window.rendered_views[&root_view.id()].name(), Some("render count: 3") @@ -6397,7 +6523,7 @@ mod tests { } let events = Rc::new(RefCell::new(Vec::new())); - let (window_1, _) = cx.add_window(|cx: &mut ViewContext| { + let window_1 = cx.add_window(|cx: &mut ViewContext| { cx.observe_window_activation({ let events = events.clone(); move |this, active, _| events.borrow_mut().push((this.0, active)) @@ -6407,7 +6533,7 @@ mod tests { }); assert_eq!(mem::take(&mut *events.borrow_mut()), [("window 1", true)]); - let (window_2, _) = cx.add_window(|cx: &mut ViewContext| { + let window_2 = cx.add_window(|cx: &mut ViewContext| { cx.observe_window_activation({ let events = events.clone(); move |this, active, _| events.borrow_mut().push((this.0, active)) @@ -6420,7 +6546,7 @@ mod tests { [("window 1", false), ("window 2", true)] ); - let (window_3, _) = cx.add_window(|cx: &mut ViewContext| { + let window_3 = cx.add_window(|cx: &mut ViewContext| { cx.observe_window_activation({ let events = events.clone(); move |this, active, _| events.borrow_mut().push((this.0, active)) diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs index f0c1699f16..c076a8a476 100644 --- a/crates/gpui/src/app/ref_counts.rs +++ b/crates/gpui/src/app/ref_counts.rs @@ -23,8 +23,10 @@ struct ElementStateRefCount { #[derive(Default)] pub struct RefCounts { + window_counts: HashMap, entity_counts: HashMap, element_state_counts: HashMap, + dropped_windows: HashSet, dropped_models: HashSet, dropped_views: HashSet<(usize, usize)>, dropped_element_states: HashSet, @@ -43,6 +45,18 @@ impl RefCounts { } } + pub fn inc_window(&mut self, window_id: usize) { + match self.window_counts.entry(window_id) { + Entry::Occupied(mut entry) => { + *entry.get_mut() += 1; + } + Entry::Vacant(entry) => { + entry.insert(1); + self.dropped_windows.remove(&window_id); + } + } + } + pub fn inc_model(&mut self, model_id: usize) { match self.entity_counts.entry(model_id) { Entry::Occupied(mut entry) => { @@ -85,6 +99,15 @@ impl RefCounts { } } + pub fn dec_window(&mut self, window_id: usize) { + let count = self.window_counts.get_mut(&window_id).unwrap(); + *count -= 1; + if *count == 0 { + self.entity_counts.remove(&window_id); + self.dropped_windows.insert(window_id); + } + } + pub fn dec_model(&mut self, model_id: usize) { let count = self.entity_counts.get_mut(&model_id).unwrap(); *count -= 1; diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 2fa8699883..0fa64f531e 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -6,7 +6,7 @@ use crate::{ platform::{Event, InputHandler, KeyDownEvent, Platform}, Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle, - WindowContext, + WindowContext, WindowHandle, }; use collections::BTreeMap; use futures::Future; @@ -148,17 +148,18 @@ impl TestAppContext { self.cx.borrow_mut().add_model(build_model) } - pub fn add_window(&mut self, build_root_view: F) -> (usize, ViewHandle) + pub fn add_window(&mut self, build_root_view: F) -> WindowHandle where T: View, F: FnOnce(&mut ViewContext) -> T, { - let (window_id, view) = self + let window = self .cx .borrow_mut() .add_window(Default::default(), build_root_view); - self.simulate_window_activation(Some(window_id)); - (window_id, view) + self.simulate_window_activation(Some(window.id())); + + WindowHandle::new(window.id(), self.cx.borrow_mut().ref_counts.clone()) } pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle From 300ce61bd0d6bf77153669eb3be044a870e76676 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 2 Aug 2023 08:25:40 -0600 Subject: [PATCH 2/9] WIP --- crates/gpui/src/app.rs | 76 ++++++++++++++----------- crates/gpui/src/app/ref_counts.rs | 5 +- crates/gpui/src/app/test_app_context.rs | 2 +- crates/gpui/src/app/window.rs | 2 +- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b2d732d170..adabcf0a8b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -498,8 +498,8 @@ pub struct AppContext { // Action Types -> Action Handlers global_actions: HashMap>, keystroke_matcher: KeymapMatcher, - next_entity_id: usize, - next_window_id: usize, + next_id: usize, + // next_window_id: usize, next_subscription_id: usize, frame_count: usize, @@ -558,8 +558,7 @@ impl AppContext { actions: Default::default(), global_actions: Default::default(), keystroke_matcher: KeymapMatcher::default(), - next_entity_id: 0, - next_window_id: 0, + next_id: 0, next_subscription_id: 0, frame_count: 0, subscriptions: Default::default(), @@ -1230,7 +1229,7 @@ impl AppContext { F: FnOnce(&mut ModelContext) -> T, { self.update(|this| { - let model_id = post_inc(&mut this.next_entity_id); + let model_id = post_inc(&mut this.next_id); let handle = ModelHandle::new(model_id, &this.ref_counts); let mut cx = ModelContext::new(this, model_id); let model = build_model(&mut cx); @@ -1310,7 +1309,7 @@ impl AppContext { F: FnOnce(&mut ViewContext) -> V, { self.update(|this| { - let window_id = post_inc(&mut this.next_window_id); + let window_id = post_inc(&mut this.next_id); let platform_window = this.platform .open_window(window_id, window_options, this.foreground.clone()); @@ -1326,7 +1325,7 @@ impl AppContext { F: FnOnce(&mut ViewContext) -> V, { self.update(|this| { - let window_id = post_inc(&mut this.next_window_id); + let window_id = post_inc(&mut this.next_id); let platform_window = this.platform.add_status_item(window_id); let window = this.build_window(window_id, platform_window, build_root_view); let root_view = window.root_view().clone().downcast::().unwrap(); @@ -3810,6 +3809,7 @@ pub struct WindowHandle { view_type: PhantomData, } +#[allow(dead_code)] impl WindowHandle { fn id(&self) -> usize { self.any_handle.id() @@ -3922,6 +3922,17 @@ impl AnyWindowHandle { pub fn id(&self) -> usize { self.window_id } + + pub fn downcast(self) -> Option> { + if TypeId::of::() == self.root_view_type { + Some(WindowHandle { + any_handle: self, + view_type: PhantomData, + }) + } else { + None + } + } } impl Drop for AnyWindowHandle { @@ -5613,20 +5624,18 @@ mod tests { child: None, }); let view_1 = window.root(cx); - let view_2 = window - .update(cx, |cx| { - let view_2 = cx.add_view(|_| View { - events: view_events.clone(), - name: "view 2".to_string(), - child: None, - }); - view_1.update(cx, |view_1, cx| { - view_1.child = Some(view_2.clone().into_any()); - cx.notify(); - }); - view_2 - }) - .unwrap(); + let view_2 = window.update(cx, |cx| { + let view_2 = cx.add_view(|_| View { + events: view_events.clone(), + name: "view 2".to_string(), + child: None, + }); + view_1.update(cx, |view_1, cx| { + view_1.child = Some(view_2.clone().into_any()); + cx.notify(); + }); + view_2 + }); let observed_events: Arc>> = Default::default(); view_1.update(cx, |_, cx| { @@ -6071,26 +6080,26 @@ mod tests { } }); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_keystroke(&Keystroke::parse("a").unwrap()) }); assert_eq!(&*actions.borrow(), &["2 a"]); actions.borrow_mut().clear(); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_keystroke(&Keystroke::parse("b").unwrap()); }); assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]); actions.borrow_mut().clear(); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_keystroke(&Keystroke::parse("c").unwrap()); }); assert_eq!(&*actions.borrow(), &["3 c"]); actions.borrow_mut().clear(); - cx.update_window(window_id, |cx| { + window.update(cx, |cx| { cx.dispatch_keystroke(&Keystroke::parse("d").unwrap()); }); assert_eq!(&*actions.borrow(), &["2 d"]); @@ -6201,7 +6210,7 @@ mod tests { // Check that global actions do not have a binding, even if a binding does exist in another view assert_eq!( - &available_actions(window_id, view_1.id(), cx), + &available_actions(window.id(), view_1.id(), cx), &[ ("test::Action1", vec![Keystroke::parse("a").unwrap()]), ("test::GlobalAction", vec![]) @@ -6210,7 +6219,7 @@ mod tests { // Check that view 1 actions and bindings are available even when called from view 2 assert_eq!( - &available_actions(window_id, view_2.id(), cx), + &available_actions(window.id(), view_2.id(), cx), &[ ("test::Action1", vec![Keystroke::parse("a").unwrap()]), ("test::Action2", vec![Keystroke::parse("b").unwrap()]), @@ -6273,7 +6282,7 @@ mod tests { ]); }); - let actions = cx.available_actions(window_id, view.id()); + let actions = cx.available_actions(window.id(), view.id()); assert_eq!( actions[0].1.as_any().downcast_ref::(), Some(&ActionWithArg { arg: false }) @@ -6559,25 +6568,25 @@ mod tests { [("window 2", false), ("window 3", true)] ); - cx.simulate_window_activation(Some(window_2)); + cx.simulate_window_activation(Some(window_2.id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 3", false), ("window 2", true)] ); - cx.simulate_window_activation(Some(window_1)); + cx.simulate_window_activation(Some(window_1.id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 2", false), ("window 1", true)] ); - cx.simulate_window_activation(Some(window_3)); + cx.simulate_window_activation(Some(window_3.id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 1", false), ("window 3", true)] ); - cx.simulate_window_activation(Some(window_3)); + cx.simulate_window_activation(Some(window_3.id())); assert_eq!(mem::take(&mut *events.borrow_mut()), []); } @@ -6633,12 +6642,13 @@ mod tests { let child_rendered = Rc::new(Cell::new(false)); let child_dropped = Rc::new(Cell::new(false)); - let (_, root_view) = cx.add_window(|cx| Parent { + let window = cx.add_window(|cx| Parent { child: Some(cx.add_view(|_| Child { rendered: child_rendered.clone(), dropped: child_dropped.clone(), })), }); + let root_view = window.root(cx); assert!(child_rendered.take()); assert!(!child_dropped.take()); diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs index c076a8a476..74563d05bc 100644 --- a/crates/gpui/src/app/ref_counts.rs +++ b/crates/gpui/src/app/ref_counts.rs @@ -23,7 +23,6 @@ struct ElementStateRefCount { #[derive(Default)] pub struct RefCounts { - window_counts: HashMap, entity_counts: HashMap, element_state_counts: HashMap, dropped_windows: HashSet, @@ -46,7 +45,7 @@ impl RefCounts { } pub fn inc_window(&mut self, window_id: usize) { - match self.window_counts.entry(window_id) { + match self.entity_counts.entry(window_id) { Entry::Occupied(mut entry) => { *entry.get_mut() += 1; } @@ -100,7 +99,7 @@ impl RefCounts { } pub fn dec_window(&mut self, window_id: usize) { - let count = self.window_counts.get_mut(&window_id).unwrap(); + let count = self.entity_counts.get_mut(&window_id).unwrap(); *count -= 1; if *count == 0 { self.entity_counts.remove(&window_id); diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 0fa64f531e..80f1037466 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -60,7 +60,7 @@ impl TestAppContext { RefCounts::new(leak_detector), (), ); - cx.next_entity_id = first_entity_id; + cx.next_id = first_entity_id; let cx = TestAppContext { cx: Rc::new(RefCell::new(cx)), foreground_platform, diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index e4beb58873..9dc5d99bc5 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1176,7 +1176,7 @@ impl<'a> WindowContext<'a> { F: FnOnce(&mut ViewContext) -> Option, { let window_id = self.window_id; - let view_id = post_inc(&mut self.next_entity_id); + let view_id = post_inc(&mut self.next_id); let mut cx = ViewContext::mutable(self, view_id); let handle = if let Some(view) = build_view(&mut cx) { let mut keymap_context = KeymapContext::default(); From 60e190e5001553e4c3c0cd472e6feb45ea07aca2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 2 Aug 2023 12:08:56 -0600 Subject: [PATCH 3/9] WIP --- crates/copilot/src/sign_in.rs | 13 ++++++------- crates/gpui/src/app.rs | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 803cb5cc85..fec8f27c97 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -4,7 +4,7 @@ use gpui::{ geometry::rect::RectF, platform::{WindowBounds, WindowKind, WindowOptions}, AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext, - ViewHandle, + WindowHandle, }; use theme::ui::modal; @@ -18,14 +18,14 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; pub fn init(cx: &mut AppContext) { if let Some(copilot) = Copilot::global(cx) { - let mut code_verification: Option> = None; + let mut code_verification: Option> = None; cx.observe(&copilot, move |copilot, cx| { let status = copilot.read(cx).status(); match &status { crate::Status::SigningIn { prompt } => { if let Some(code_verification_handle) = code_verification.as_mut() { - let window_id = code_verification_handle.window_id(); + let window_id = code_verification_handle.id(); let updated = cx.update_window(window_id, |cx| { code_verification_handle.update(cx, |code_verification, cx| { code_verification.set_status(status.clone(), cx) @@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) { fn create_copilot_auth_window( cx: &mut AppContext, status: &Status, -) -> ViewHandle { +) -> WindowHandle { let window_size = theme::current(cx).copilot.modal.dimensions(); let window_options = WindowOptions { bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)), @@ -78,10 +78,9 @@ fn create_copilot_auth_window( is_movable: true, screen: None, }; - let (_, view) = cx.add_window(window_options, |_cx| { + cx.add_window(window_options, |_cx| { CopilotCodeVerification::new(status.clone()) - }); - view + }) } pub struct CopilotCodeVerification { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index adabcf0a8b..9c0e50647c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3811,10 +3811,6 @@ pub struct WindowHandle { #[allow(dead_code)] impl WindowHandle { - fn id(&self) -> usize { - self.any_handle.id() - } - fn new(window_id: usize, ref_counts: Arc>) -> Self { WindowHandle { any_handle: AnyWindowHandle::new::(window_id, ref_counts), @@ -3822,7 +3818,11 @@ impl WindowHandle { } } - fn root(&self, cx: &impl BorrowAppContext) -> ViewHandle { + pub fn id(&self) -> usize { + self.any_handle.id() + } + + pub fn root(&self, cx: &impl BorrowAppContext) -> ViewHandle { self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap()) } From 884cee6dfda9f4b887976cb14f87e82ac6b87fc0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 2 Aug 2023 14:05:03 -0600 Subject: [PATCH 4/9] Get tests compiling returning WindowHandle from add_window --- crates/collab/src/tests.rs | 5 +- crates/collab/src/tests/integration_tests.rs | 52 +- .../src/incoming_call_notification.rs | 4 +- .../src/project_shared_notification.rs | 4 +- crates/command_palette/src/command_palette.rs | 4 +- crates/copilot/src/sign_in.rs | 8 +- crates/diagnostics/src/diagnostics.rs | 8 +- crates/editor/src/editor_tests.rs | 627 ++++++++++-------- crates/editor/src/element.rs | 30 +- crates/editor/src/inlay_hint_cache.rs | 30 +- .../src/test/editor_lsp_test_context.rs | 5 +- crates/editor/src/test/editor_test_context.rs | 12 +- crates/file_finder/src/file_finder.rs | 230 +++---- crates/gpui/src/app.rs | 26 +- crates/gpui/src/app/window.rs | 8 +- crates/language_tools/src/lsp_log_tests.rs | 4 +- crates/project_panel/src/project_panel.rs | 32 +- crates/project_symbols/src/project_symbols.rs | 4 +- crates/search/src/buffer_search.rs | 19 +- crates/search/src/project_search.rs | 16 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/workspace/src/pane.rs | 30 +- crates/workspace/src/workspace.rs | 108 +-- crates/zed/src/zed.rs | 34 +- 24 files changed, 726 insertions(+), 578 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index b1d0bedb2c..4804f5b0f1 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -495,8 +495,9 @@ impl TestClient { // We use a workspace container so that we don't need to remove the window in order to // drop the workspace and we can use a ViewHandle instead. - let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None }); - let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|_| WorkspaceContainer { workspace: None }); + let container = window.root(cx); + let workspace = window.add_view(cx, |cx| Workspace::test_new(project.clone(), cx)); container.update(cx, |container, cx| { container.workspace = Some(workspace.downgrade()); cx.notify(); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index ab94f16a07..1a8e6d938d 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT}; use collections::HashSet; use editor::{ test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, - ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, - Undo, + ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo, }; use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; @@ -1208,7 +1207,7 @@ async fn test_share_project( cx_c: &mut TestAppContext, ) { deterministic.forbid_parking(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); + let window_b = cx_b.add_window(|_| EmptyView); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -1316,7 +1315,7 @@ async fn test_share_project( .await .unwrap(); - let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx)); + let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx)); // Client A sees client B's selection deterministic.run_until_parked(); @@ -1499,8 +1498,8 @@ async fn test_host_disconnect( deterministic.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - let (window_id_b, workspace_b) = - cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let workspace_b = window_b.root(cx_b); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "b.txt"), None, true, cx) @@ -1509,9 +1508,7 @@ async fn test_host_disconnect( .unwrap() .downcast::() .unwrap(); - assert!(cx_b - .read_window(window_id_b, |cx| editor_b.is_focused(cx)) - .unwrap()); + assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx))); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); assert!(cx_b.is_window_edited(workspace_b.window_id())); @@ -1525,7 +1522,7 @@ async fn test_host_disconnect( assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); // Ensure client B's edited state is reset and that the whole window is blurred. - cx_b.read_window(window_id_b, |cx| { + window_b.read_with(cx_b, |cx| { assert_eq!(cx.focused_view_id(), None); }); assert!(!cx_b.is_window_edited(workspace_b.window_id())); @@ -3445,13 +3442,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let (window_a, _) = cx_a.add_window(|_| EmptyView); - let editor_a = cx_a.add_view(window_a, |cx| { - Editor::for_buffer(buffer_a, Some(project_a), cx) - }); + let window_a = cx_a.add_window(|_| EmptyView); + let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); let mut editor_cx_a = EditorTestContext { cx: cx_a, - window_id: window_a, + window_id: window_a.id(), editor: editor_a, }; @@ -3460,13 +3455,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); - let editor_b = cx_b.add_view(window_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b), cx) - }); + let window_b = cx_b.add_window(|_| EmptyView); + let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); let mut editor_cx_b = EditorTestContext { cx: cx_b, - window_id: window_b, + window_id: window_b.id(), editor: editor_b, }; @@ -4205,8 +4198,8 @@ async fn test_collaborating_with_completion( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); - let editor_b = cx_b.add_view(window_b, |cx| { + let window_b = cx_b.add_window(|_| EmptyView); + let editor_b = window_b.add_view(cx_b, |cx| { Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) }); @@ -5316,7 +5309,8 @@ async fn test_collaborating_with_code_actions( // Join the project as client B. let project_b = client_b.build_remote_project(project_id, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let workspace_b = window_b.root(cx_b); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -5540,7 +5534,8 @@ async fn test_collaborating_with_renames( .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx)); + let workspace_b = window_b.root(cx_b); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "one.rs"), None, true, cx) @@ -5571,6 +5566,7 @@ async fn test_collaborating_with_renames( .unwrap(); prepare_rename.await.unwrap(); editor_b.update(cx_b, |editor, cx| { + use editor::ToOffset; let rename = editor.pending_rename().unwrap(); let buffer = editor.buffer().read(cx).snapshot(cx); assert_eq!( @@ -7601,8 +7597,8 @@ async fn test_on_input_format_from_host_to_guest( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let (window_a, _) = cx_a.add_window(|_| EmptyView); - let editor_a = cx_a.add_view(window_a, |cx| { + let window_a = cx_a.add_window(|_| EmptyView); + let editor_a = window_a.add_view(cx_a, |cx| { Editor::for_buffer(buffer_a, Some(project_a.clone()), cx) }); @@ -7730,8 +7726,8 @@ async fn test_on_input_format_from_guest_to_host( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); - let editor_b = cx_b.add_view(window_b, |cx| { + let window_b = cx_b.add_window(|_| EmptyView); + let editor_b = window_b.add_view(cx_b, |cx| { Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) }); diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 4066b5b229..a9c5e697a5 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -31,7 +31,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for screen in cx.platform().screens() { let screen_bounds = screen.bounds(); - let (window_id, _) = cx.add_window( + let window = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( screen_bounds.upper_right() @@ -49,7 +49,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { |_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()), ); - notification_windows.push(window_id); + notification_windows.push(window.id()); } } } diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index fea6118bdf..03ab91623b 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -26,7 +26,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for screen in cx.platform().screens() { let screen_bounds = screen.bounds(); - let (window_id, _) = cx.add_window( + let window = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING), @@ -52,7 +52,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { notification_windows .entry(*project_id) .or_insert(Vec::new()) - .push(window_id); + .push(window.id()); } } room::Event::RemoteProjectUnshared { project_id } => { diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 7461fb28c7..7d4b4126b7 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -295,7 +295,9 @@ mod tests { let app_state = init_test(cx); let project = Project::test(app_state.fs.clone(), [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let editor = cx.add_view(window_id, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index fec8f27c97..659bee7445 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -27,7 +27,7 @@ pub fn init(cx: &mut AppContext) { if let Some(code_verification_handle) = code_verification.as_mut() { let window_id = code_verification_handle.id(); let updated = cx.update_window(window_id, |cx| { - code_verification_handle.update(cx, |code_verification, cx| { + code_verification_handle.update_root(cx, |code_verification, cx| { code_verification.set_status(status.clone(), cx) }); cx.activate_window(); @@ -41,9 +41,9 @@ pub fn init(cx: &mut AppContext) { } Status::Authorized | Status::Unauthorized => { if let Some(code_verification) = code_verification.as_ref() { - let window_id = code_verification.window_id(); + let window_id = code_verification.id(); cx.update_window(window_id, |cx| { - code_verification.update(cx, |code_verification, cx| { + code_verification.update_root(cx, |code_verification, cx| { code_verification.set_status(status, cx) }); @@ -54,7 +54,7 @@ pub fn init(cx: &mut AppContext) { } _ => { if let Some(code_verification) = code_verification.take() { - cx.update_window(code_verification.window_id(), |cx| cx.remove_window()); + code_verification.update(cx, |cx| cx.remove_window()); } } } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d0cd437946..2444465be6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -855,7 +855,9 @@ mod tests { let language_server_id = LanguageServerId(0); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); // Create some diagnostics project.update(cx, |project, cx| { @@ -1248,7 +1250,9 @@ mod tests { let server_id_1 = LanguageServerId(100); let server_id_2 = LanguageServerId(101); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let view = cx.add_view(window_id, |cx| { ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index eb03d2bdc0..96921643d4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -48,36 +48,40 @@ fn test_edit_events(cx: &mut TestAppContext) { }); let events = Rc::new(RefCell::new(Vec::new())); - let (_, editor1) = cx.add_window({ - let events = events.clone(); - |cx| { - cx.subscribe(&cx.handle(), move |_, _, event, _| { - if matches!( - event, - Event::Edited | Event::BufferEdited | Event::DirtyChanged - ) { - events.borrow_mut().push(("editor1", event.clone())); - } - }) - .detach(); - Editor::for_buffer(buffer.clone(), None, cx) - } - }); - let (_, editor2) = cx.add_window({ - let events = events.clone(); - |cx| { - cx.subscribe(&cx.handle(), move |_, _, event, _| { - if matches!( - event, - Event::Edited | Event::BufferEdited | Event::DirtyChanged - ) { - events.borrow_mut().push(("editor2", event.clone())); - } - }) - .detach(); - Editor::for_buffer(buffer.clone(), None, cx) - } - }); + let editor1 = cx + .add_window({ + let events = events.clone(); + |cx| { + cx.subscribe(&cx.handle(), move |_, _, event, _| { + if matches!( + event, + Event::Edited | Event::BufferEdited | Event::DirtyChanged + ) { + events.borrow_mut().push(("editor1", event.clone())); + } + }) + .detach(); + Editor::for_buffer(buffer.clone(), None, cx) + } + }) + .detach(cx); + let editor2 = cx + .add_window({ + let events = events.clone(); + |cx| { + cx.subscribe(&cx.handle(), move |_, _, event, _| { + if matches!( + event, + Event::Edited | Event::BufferEdited | Event::DirtyChanged + ) { + events.borrow_mut().push(("editor2", event.clone())); + } + }) + .detach(); + Editor::for_buffer(buffer.clone(), None, cx) + } + }) + .detach(cx); assert_eq!(mem::take(&mut *events.borrow_mut()), []); // Mutating editor 1 will emit an `Edited` event only for that editor. @@ -173,7 +177,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx)); let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval()); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx)); + let editor = cx + .add_window(|cx| build_editor(buffer.clone(), cx)) + .detach(cx); editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); @@ -343,10 +349,12 @@ fn test_ime_composition(cx: &mut TestAppContext) { fn test_selection_with_mouse(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); - build_editor(buffer, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); }); @@ -410,10 +418,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) { fn test_canceling_pending_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); @@ -456,10 +466,12 @@ fn test_clone(cx: &mut TestAppContext) { true, ); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&text, cx); - build_editor(buffer, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&text, cx); + build_editor(buffer, cx) + }) + .detach(cx); editor.update(cx, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone())); @@ -473,9 +485,11 @@ fn test_clone(cx: &mut TestAppContext) { ); }); - let (_, cloned_editor) = editor.update(cx, |editor, cx| { - cx.add_window(Default::default(), |cx| editor.clone(cx)) - }); + let cloned_editor = editor + .update(cx, |editor, cx| { + cx.add_window(Default::default(), |cx| editor.clone(cx)) + }) + .detach(cx); let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)); let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)); @@ -509,7 +523,9 @@ async fn test_navigation_history(cx: &mut TestAppContext) { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); cx.add_view(window_id, |cx| { let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); @@ -618,10 +634,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) { fn test_cancel(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); @@ -661,9 +679,10 @@ fn test_cancel(cx: &mut TestAppContext) { fn test_fold_action(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple( - &" + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple( + &" impl Foo { // Hello! @@ -680,11 +699,12 @@ fn test_fold_action(cx: &mut TestAppContext) { } } " - .unindent(), - cx, - ); - build_editor(buffer.clone(), cx) - }); + .unindent(), + cx, + ); + build_editor(buffer.clone(), cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -752,7 +772,9 @@ fn test_move_cursor(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx)); - let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx)); + let view = cx + .add_window(|cx| build_editor(buffer.clone(), cx)) + .detach(cx); buffer.update(cx, |buffer, cx| { buffer.edit( @@ -827,10 +849,12 @@ fn test_move_cursor(cx: &mut TestAppContext) { fn test_move_cursor_multibyte(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); - build_editor(buffer.clone(), cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); + build_editor(buffer.clone(), cx) + }) + .detach(cx); assert_eq!('ⓐ'.len_utf8(), 3); assert_eq!('α'.len_utf8(), 2); @@ -932,10 +956,12 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); - build_editor(buffer.clone(), cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); + build_editor(buffer.clone(), cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); @@ -982,10 +1008,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { fn test_beginning_end_of_line(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\n def", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\n def", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -1145,10 +1173,12 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { fn test_prev_next_word_boundary(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -1197,10 +1227,13 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = + MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.set_wrap_width(Some(140.), cx); @@ -1530,10 +1563,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { fn test_delete_to_word_boundary(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("one two three four", cx); - build_editor(buffer.clone(), cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("one two three four", cx); + build_editor(buffer.clone(), cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -1566,10 +1601,12 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { fn test_newline(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); - build_editor(buffer.clone(), cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); + build_editor(buffer.clone(), cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -1589,9 +1626,10 @@ fn test_newline(cx: &mut TestAppContext) { fn test_newline_with_old_selections(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple( - " + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple( + " a b( X @@ -1600,19 +1638,20 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { X ) " - .unindent() - .as_str(), - cx, - ); - let mut editor = build_editor(buffer.clone(), cx); - editor.change_selections(None, cx, |s| { - s.select_ranges([ - Point::new(2, 4)..Point::new(2, 5), - Point::new(5, 4)..Point::new(5, 5), - ]) - }); - editor - }); + .unindent() + .as_str(), + cx, + ); + let mut editor = build_editor(buffer.clone(), cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([ + Point::new(2, 4)..Point::new(2, 5), + Point::new(5, 4)..Point::new(5, 5), + ]) + }); + editor + }) + .detach(cx); editor.update(cx, |editor, cx| { // Edit the buffer directly, deleting ranges surrounding the editor's selections @@ -1817,12 +1856,14 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) { fn test_insert_with_old_selections(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); - let mut editor = build_editor(buffer.clone(), cx); - editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20])); - editor - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); + let mut editor = build_editor(buffer.clone(), cx); + editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20])); + editor + }) + .detach(cx); editor.update(cx, |editor, cx| { // Edit the buffer directly, deleting ranges surrounding the editor's selections @@ -2329,10 +2370,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) { fn test_delete_line(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2352,10 +2395,12 @@ fn test_delete_line(cx: &mut TestAppContext) { ); }); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)]) @@ -2654,10 +2699,12 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) { fn test_duplicate_line(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2680,10 +2727,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) { ); }); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2707,10 +2756,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) { fn test_move_line_up_down(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -2806,10 +2857,12 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); - build_editor(buffer, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); + build_editor(buffer, cx) + }) + .detach(cx); editor.update(cx, |editor, cx| { let snapshot = editor.buffer.read(cx).snapshot(cx); editor.insert_blocks( @@ -2834,102 +2887,94 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { fn test_transpose(cx: &mut TestAppContext) { init_test(cx, |_| {}); - _ = cx - .add_window(|cx| { - let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); + _ = cx.add_window(|cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); - editor.change_selections(None, cx, |s| s.select_ranges([1..1])); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bac"); - assert_eq!(editor.selections.ranges(cx), [2..2]); + editor.change_selections(None, cx, |s| s.select_ranges([1..1])); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bac"); + assert_eq!(editor.selections.ranges(cx), [2..2]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bca"); - assert_eq!(editor.selections.ranges(cx), [3..3]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bca"); + assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bac"); - assert_eq!(editor.selections.ranges(cx), [3..3]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bac"); + assert_eq!(editor.selections.ranges(cx), [3..3]); - editor - }) - .1; + editor + }); - _ = cx - .add_window(|cx| { - let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + _ = cx.add_window(|cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); - editor.change_selections(None, cx, |s| s.select_ranges([3..3])); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "acb\nde"); - assert_eq!(editor.selections.ranges(cx), [3..3]); + editor.change_selections(None, cx, |s| s.select_ranges([3..3])); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acb\nde"); + assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.change_selections(None, cx, |s| s.select_ranges([4..4])); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "acbd\ne"); - assert_eq!(editor.selections.ranges(cx), [5..5]); + editor.change_selections(None, cx, |s| s.select_ranges([4..4])); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbd\ne"); + assert_eq!(editor.selections.ranges(cx), [5..5]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "acbde\n"); - assert_eq!(editor.selections.ranges(cx), [6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbde\n"); + assert_eq!(editor.selections.ranges(cx), [6..6]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "acbd\ne"); - assert_eq!(editor.selections.ranges(cx), [6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbd\ne"); + assert_eq!(editor.selections.ranges(cx), [6..6]); - editor - }) - .1; + editor + }); - _ = cx - .add_window(|cx| { - let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + _ = cx.add_window(|cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); - editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bacd\ne"); - assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); + editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bacd\ne"); + assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bcade\n"); - assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcade\n"); + assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bcda\ne"); - assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcda\ne"); + assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bcade\n"); - assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcade\n"); + assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "bcaed\n"); - assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcaed\n"); + assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]); - editor - }) - .1; + editor + }); - _ = cx - .add_window(|cx| { - let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx); + _ = cx.add_window(|cx| { + let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx); - editor.change_selections(None, cx, |s| s.select_ranges([4..4])); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "🏀🍐✋"); - assert_eq!(editor.selections.ranges(cx), [8..8]); + editor.change_selections(None, cx, |s| s.select_ranges([4..4])); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "🏀🍐✋"); + assert_eq!(editor.selections.ranges(cx), [8..8]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "🏀✋🍐"); - assert_eq!(editor.selections.ranges(cx), [11..11]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "🏀✋🍐"); + assert_eq!(editor.selections.ranges(cx), [11..11]); - editor.transpose(&Default::default(), cx); - assert_eq!(editor.text(cx), "🏀🍐✋"); - assert_eq!(editor.selections.ranges(cx), [11..11]); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "🏀🍐✋"); + assert_eq!(editor.selections.ranges(cx), [11..11]); - editor - }) - .1; + editor + }); } #[gpui::test] @@ -3132,10 +3177,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { fn test_select_all(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); assert_eq!( @@ -3149,10 +3196,12 @@ fn test_select_all(cx: &mut TestAppContext) { fn test_select_line(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -3196,10 +3245,12 @@ fn test_select_line(cx: &mut TestAppContext) { fn test_split_selection_into_lines(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -3267,10 +3318,12 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) { fn test_add_selection_above_below(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, view) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); - build_editor(buffer, cx) - }); + let view = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); + build_editor(buffer, cx) + }) + .detach(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -3555,7 +3608,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, view) = cx.add_window(|cx| build_editor(buffer, cx)); + let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -3718,7 +3771,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx)) .await; @@ -4281,7 +4334,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, view) = cx.add_window(|cx| build_editor(buffer, cx)); + let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -4429,7 +4482,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -4519,7 +4572,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) { ); let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor.update(cx, |editor, cx| { let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); @@ -4649,7 +4702,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); assert!(cx.read(|cx| editor.is_dirty(cx))); @@ -4761,7 +4814,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); assert!(cx.read(|cx| editor.is_dirty(cx))); @@ -4875,7 +4928,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); let format = editor.update(cx, |editor, cx| { @@ -5653,7 +5706,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { multibuffer }); - let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx)); + let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); view.update(cx, |view, cx| { assert_eq!(view.text(cx), "aaaa\nbbbb"); view.change_selections(None, cx, |s| { @@ -5723,7 +5776,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { multibuffer }); - let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx)); + let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); view.update(cx, |view, cx| { let (expected_text, selection_ranges) = marked_text_ranges( indoc! {" @@ -5799,22 +5852,24 @@ fn test_refresh_selections(cx: &mut TestAppContext) { multibuffer }); - let (_, editor) = cx.add_window(|cx| { - let mut editor = build_editor(multibuffer.clone(), cx); - let snapshot = editor.snapshot(cx); - editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) - }); - editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx); - assert_eq!( - editor.selections.ranges(cx), - [ - Point::new(1, 3)..Point::new(1, 3), - Point::new(2, 1)..Point::new(2, 1), - ] - ); - editor - }); + let editor = cx + .add_window(|cx| { + let mut editor = build_editor(multibuffer.clone(), cx); + let snapshot = editor.snapshot(cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) + }); + editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx); + assert_eq!( + editor.selections.ranges(cx), + [ + Point::new(1, 3)..Point::new(1, 3), + Point::new(2, 1)..Point::new(2, 1), + ] + ); + editor + }) + .detach(cx); // Refreshing selections is a no-op when excerpts haven't changed. editor.update(cx, |editor, cx| { @@ -5884,16 +5939,18 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { multibuffer }); - let (_, editor) = cx.add_window(|cx| { - let mut editor = build_editor(multibuffer.clone(), cx); - let snapshot = editor.snapshot(cx); - editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx); - assert_eq!( - editor.selections.ranges(cx), - [Point::new(1, 3)..Point::new(1, 3)] - ); - editor - }); + let editor = cx + .add_window(|cx| { + let mut editor = build_editor(multibuffer.clone(), cx); + let snapshot = editor.snapshot(cx); + editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx); + assert_eq!( + editor.selections.ranges(cx), + [Point::new(1, 3)..Point::new(1, 3)] + ); + editor + }) + .detach(cx); multibuffer.update(cx, |multibuffer, cx| { multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); @@ -5956,7 +6013,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, view) = cx.add_window(|cx| build_editor(buffer, cx)); + let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -5992,10 +6049,12 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { fn test_highlighted_ranges(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); - build_editor(buffer.clone(), cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); + build_editor(buffer.clone(), cx) + }) + .detach(cx); editor.update(cx, |editor, cx| { struct Type1; @@ -6084,16 +6143,20 @@ async fn test_following(cx: &mut gpui::TestAppContext) { .unwrap(); cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)) }); - let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx)); - let (_, follower) = cx.update(|cx| { - cx.add_window( - WindowOptions { - bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))), - ..Default::default() - }, - |cx| build_editor(buffer.clone(), cx), - ) - }); + let leader = cx + .add_window(|cx| build_editor(buffer.clone(), cx)) + .detach(cx); + let follower = cx + .update(|cx| { + cx.add_window( + WindowOptions { + bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))), + ..Default::default() + }, + |cx| build_editor(buffer.clone(), cx), + ) + }) + .detach(cx); let is_still_following = Rc::new(RefCell::new(true)); let follower_edit_event_count = Rc::new(RefCell::new(0)); @@ -6224,7 +6287,9 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let leader = pane.update(cx, |_, cx| { @@ -6968,7 +7033,7 @@ async fn test_copilot_multibuffer( ); multibuffer }); - let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx)); + let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); handle_copilot_completion_request( &copilot_lsp, @@ -7098,7 +7163,7 @@ async fn test_copilot_disabled_globs( ); multibuffer }); - let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx)); + let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); let mut copilot_requests = copilot_lsp .handle_request::(move |_params, _cx| async move { @@ -7177,7 +7242,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) { .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -7282,7 +7349,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let _buffer = project .update(cx, |project, cx| { project.open_local_buffer("/a/main.rs", cx) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 750beaea13..dc40e7fb85 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3002,10 +3002,12 @@ mod tests { fn test_layout_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); - Editor::new(EditorMode::Full, buffer, None, None, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); + Editor::new(EditorMode::Full, buffer, None, None, cx) + }) + .detach(cx); let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let layouts = editor.update(cx, |editor, cx| { @@ -3021,10 +3023,12 @@ mod tests { fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple("", cx); - Editor::new(EditorMode::Full, buffer, None, None, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple("", cx); + Editor::new(EditorMode::Full, buffer, None, None, cx) + }) + .detach(cx); editor.update(cx, |editor, cx| { editor.set_placeholder_text("hello", cx); @@ -3231,10 +3235,12 @@ mod tests { info!( "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'" ); - let (_, editor) = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&input_text, cx); - Editor::new(editor_mode, buffer, None, None, cx) - }); + let editor = cx + .add_window(|cx| { + let buffer = MultiBuffer::build_simple(&input_text, cx); + Editor::new(editor_mode, buffer, None, None, cx) + }) + .detach(cx); let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (_, layout_state) = editor.update(cx, |editor, cx| { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 63076ba234..089cbb2995 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1136,7 +1136,9 @@ mod tests { ) .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -1836,7 +1838,9 @@ mod tests { .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -1989,7 +1993,9 @@ mod tests { project.update(cx, |project, _| { project.languages().add(Arc::clone(&language)) }); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -2075,8 +2081,9 @@ mod tests { deterministic.run_until_parked(); cx.foreground().run_until_parked(); - let (_, editor) = - cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); + let editor = cx + .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) + .detach(cx); let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); let closure_editor_edited = Arc::clone(&editor_edited); @@ -2328,7 +2335,9 @@ all hints should be invalidated and requeried for all of its visible excerpts" project.update(cx, |project, _| { project.languages().add(Arc::clone(&language)) }); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -2373,8 +2382,9 @@ all hints should be invalidated and requeried for all of its visible excerpts" deterministic.run_until_parked(); cx.foreground().run_until_parked(); - let (_, editor) = - cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); + let editor = cx + .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) + .detach(cx); let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); let closure_editor_edited = Arc::clone(&editor_edited); @@ -2562,7 +2572,9 @@ all hints should be invalidated and requeried for all of its visible excerpts" let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 0fe49d4d04..f53115f224 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> { .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) .await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); project .update(cx, |project, cx| { project.find_or_create_local_worktree("/root", true, cx) @@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> { Self { cx: EditorTestContext { cx, - window_id, + window_id: window.id(), editor, }, lsp, diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index bac70f139a..c7ea1b4f38 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -32,16 +32,14 @@ impl<'a> EditorTestContext<'a> { let buffer = project .update(cx, |project, cx| project.create_buffer("", None, cx)) .unwrap(); - let (window_id, editor) = cx.update(|cx| { - cx.add_window(Default::default(), |cx| { - cx.focus_self(); - build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx) - }) + let window = cx.add_window(|cx| { + cx.focus_self(); + build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx) }); - + let editor = window.root(cx); Self { cx, - window_id, + window_id: window.id(), editor, } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index b6701f12d6..2c9d9c0c71 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -617,8 +617,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - cx.dispatch_action(window_id, Toggle); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + cx.dispatch_action(window.id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); finder @@ -631,8 +632,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); + cx.dispatch_action(window.id(), SelectNext); + cx.dispatch_action(window.id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -671,8 +672,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - cx.dispatch_action(window_id, Toggle); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + cx.dispatch_action(window.id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); let file_query = &first_file_name[..3]; @@ -704,8 +706,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); + cx.dispatch_action(window.id(), SelectNext); + cx.dispatch_action(window.id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -754,8 +756,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - cx.dispatch_action(window_id, Toggle); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + cx.dispatch_action(window.id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); let file_query = &first_file_name[..3]; @@ -787,8 +790,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); + cx.dispatch_action(window.id(), SelectNext); + cx.dispatch_action(window.id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -837,19 +840,23 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - None, - Vec::new(), + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); + let finder = cx + .add_window(|cx| { + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + Vec::new(), + cx, + ), cx, - ), - cx, - ) - }); + ) + }) + .detach(cx); let query = test_path_like("hi"); finder @@ -931,19 +938,23 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - None, - Vec::new(), + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); + let finder = cx + .add_window(|cx| { + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + Vec::new(), + cx, + ), cx, - ), - cx, - ) - }); + ) + }) + .detach(cx); finder .update(cx, |f, cx| { f.delegate_mut().spawn_search(test_path_like("hi"), cx) @@ -967,19 +978,23 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - None, - Vec::new(), + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); + let finder = cx + .add_window(|cx| { + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + Vec::new(), + cx, + ), cx, - ), - cx, - ) - }); + ) + }) + .detach(cx); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -1015,61 +1030,6 @@ mod tests { finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0)); } - #[gpui::test] - async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) { - let app_state = init_test(cx); - app_state - .fs - .as_fake() - .insert_tree( - "/root", - json!({ - "dir1": { "a.txt": "" }, - "dir2": { "a.txt": "" } - }), - ) - .await; - - let project = Project::test( - app_state.fs.clone(), - ["/root/dir1".as_ref(), "/root/dir2".as_ref()], - cx, - ) - .await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - None, - Vec::new(), - cx, - ), - cx, - ) - }); - - // Run a search that matches two files with the same relative path. - finder - .update(cx, |f, cx| { - f.delegate_mut().spawn_search(test_path_like("a.t"), cx) - }) - .await; - - // Can switch between different matches with the same relative path. - finder.update(cx, |finder, cx| { - let delegate = finder.delegate_mut(); - assert_eq!(delegate.matches.len(), 2); - assert_eq!(delegate.selected_index(), 0); - delegate.set_selected_index(1, cx); - assert_eq!(delegate.selected_index(), 1); - delegate.set_selected_index(0, cx); - assert_eq!(delegate.selected_index(), 0); - }); - } - #[gpui::test] async fn test_path_distance_ordering(cx: &mut TestAppContext) { let app_state = init_test(cx); @@ -1089,7 +1049,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -1103,18 +1065,20 @@ mod tests { worktree_id, path: Arc::from(Path::new("/root/dir2/b.txt")), })); - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - b_path, - Vec::new(), + let finder = cx + .add_window(|cx| { + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + b_path, + Vec::new(), + cx, + ), cx, - ), - cx, - ) - }); + ) + }) + .detach(cx); finder .update(cx, |f, cx| { @@ -1151,19 +1115,23 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = cx.add_window(|cx| { - Picker::new( - FileFinderDelegate::new( - workspace.downgrade(), - workspace.read(cx).project().clone(), - None, - Vec::new(), + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); + let finder = cx + .add_window(|cx| { + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + Vec::new(), + cx, + ), cx, - ), - cx, - ) - }); + ) + }) + .detach(cx); finder .update(cx, |f, cx| { f.delegate_mut().spawn_search(test_path_like("dir"), cx) @@ -1198,7 +1166,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -1404,7 +1374,9 @@ mod tests { .detach(); deterministic.run_until_parked(); - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1,); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9c0e50647c..45169ed3af 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1424,7 +1424,7 @@ impl AppContext { &mut self, window_id: usize, build_root_view: F, - ) -> Option> + ) -> Option> where V: View, F: FnOnce(&mut ViewContext) -> V, @@ -3826,6 +3826,15 @@ impl WindowHandle { self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap()) } + /// Keep this window open until it's explicitly closed. + // + // TODO: Implement window dropping behavior when we don't call this. + pub fn detach(mut self, cx: &impl BorrowAppContext) -> ViewHandle { + let root = self.root(cx); + self.any_handle.ref_counts.take(); + root + } + pub fn read_with(&self, cx: &C, read: F) -> R where C: BorrowAppContext, @@ -3893,7 +3902,7 @@ impl WindowHandle { pub struct AnyWindowHandle { window_id: usize, root_view_type: TypeId, - ref_counts: Arc>, + ref_counts: Option>>, #[cfg(any(test, feature = "test-support"))] handle_id: usize, @@ -3913,7 +3922,7 @@ impl AnyWindowHandle { Self { window_id, root_view_type: TypeId::of::(), - ref_counts, + ref_counts: Some(ref_counts), #[cfg(any(test, feature = "test-support"))] handle_id, } @@ -3937,7 +3946,16 @@ impl AnyWindowHandle { impl Drop for AnyWindowHandle { fn drop(&mut self) { - self.ref_counts.lock().dec_window(self.window_id) + if let Some(ref_counts) = self.ref_counts.as_ref() { + ref_counts.lock().dec_window(self.window_id); + + #[cfg(any(test, feature = "test-support"))] + ref_counts + .lock() + .leak_detector + .lock() + .handle_dropped(self.window_id, self.handle_id); + } } } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 9dc5d99bc5..7cdcbc2c8f 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -15,7 +15,7 @@ use crate::{ util::post_inc, Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription, - View, ViewContext, ViewHandle, WindowInvalidation, + View, ViewContext, ViewHandle, WindowHandle, WindowInvalidation, }; use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; @@ -1151,15 +1151,15 @@ impl<'a> WindowContext<'a> { self.window.platform_window.prompt(level, msg, answers) } - pub fn replace_root_view(&mut self, build_root_view: F) -> ViewHandle + pub fn replace_root_view(&mut self, build_root_view: F) -> WindowHandle where V: View, F: FnOnce(&mut ViewContext) -> V, { let root_view = self.add_view(|cx| build_root_view(cx)); - self.window.root_view = Some(root_view.clone().into_any()); self.window.focused_view_id = Some(root_view.id()); - root_view + self.window.root_view = Some(root_view.into_any()); + WindowHandle::new(self.window_id, self.ref_counts.clone()) } pub fn add_view(&mut self, build_view: F) -> ViewHandle diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index d4a16b5758..ce05a417ad 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/crates/language_tools/src/lsp_log_tests.rs @@ -61,7 +61,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) { .receive_notification::() .await; - let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx)); + let log_view = cx + .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx)) + .detach(cx); language_server.notify::(lsp::LogMessageParams { message: "hello from the server".into(), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0be52646e6..fdc5ea108a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1780,7 +1780,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..50, cx), @@ -1868,7 +1870,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "root1", cx); @@ -2219,7 +2223,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "root1", cx); @@ -2319,7 +2325,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); panel.update(cx, |panel, cx| { @@ -2392,7 +2400,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); toggle_expand_dir(&panel, "src/test", cx); @@ -2481,7 +2491,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "src/", cx); @@ -2627,7 +2639,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let new_search_events_count = Arc::new(AtomicUsize::new(0)); @@ -2714,7 +2728,9 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); panel.update(cx, |panel, cx| { diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index cbf914230d..8471f3a3a7 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -326,7 +326,9 @@ mod tests { }, ); - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let window_id = window.id(); // Create the project symbols view. let symbols = cx.add_view(window_id, |cx| { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 45842aa561..1e635432bd 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -849,11 +849,13 @@ mod tests { cx, ) }); - let (window_id, _root_view) = cx.add_window(|_| EmptyView); + let window = cx.add_window(|_| EmptyView); - let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); + let editor = cx.add_view(window.id(), |cx| { + Editor::for_buffer(buffer.clone(), None, cx) + }); - let search_bar = cx.add_view(window_id, |cx| { + let search_bar = cx.add_view(window.id(), |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); search_bar.show(cx); @@ -1229,7 +1231,8 @@ mod tests { "Should pick a query with multiple results" ); let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); - let (window_id, _root_view) = cx.add_window(|_| EmptyView); + let window = cx.add_window(|_| EmptyView); + let window_id = window.id(); let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); @@ -1416,11 +1419,13 @@ mod tests { "# .unindent(); let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); - let (window_id, _root_view) = cx.add_window(|_| EmptyView); + let window = cx.add_window(|_| EmptyView); - let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); + let editor = cx.add_view(window.id(), |cx| { + Editor::for_buffer(buffer.clone(), None, cx) + }); - let search_bar = cx.add_view(window_id, |cx| { + let search_bar = cx.add_view(window.id(), |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); search_bar.show(cx); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 1b4e32f4b8..e57edd3b14 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1447,7 +1447,9 @@ pub mod tests { .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let search = cx.add_model(|cx| ProjectSearch::new(project, cx)); - let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx)); + let search_view = cx + .add_window(|cx| ProjectSearchView::new(search.clone(), cx)) + .detach(cx); search_view.update(cx, |search_view, cx| { search_view @@ -1564,7 +1566,9 @@ pub mod tests { ) .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let active_item = cx.read(|cx| { workspace @@ -1748,7 +1752,9 @@ pub mod tests { let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); let active_item = cx.read(|cx| { workspace @@ -1866,7 +1872,9 @@ pub mod tests { ) .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); workspace.update(cx, |workspace, cx| { ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) }); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e108a05ccc..874978b4fc 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1070,7 +1070,9 @@ mod tests { }); let project = Project::test(params.fs.clone(), [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); (project, workspace) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index ee658c9cc9..98883fac33 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1972,7 +1972,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); pane.update(cx, |pane, cx| { @@ -1987,7 +1988,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -2065,7 +2067,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -2141,7 +2144,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // singleton view @@ -2209,7 +2213,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); add_labeled_item(&pane, "A", false, cx); @@ -2256,7 +2261,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); @@ -2276,7 +2282,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); add_labeled_item(&pane, "A", true, cx); @@ -2299,7 +2306,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); @@ -2319,7 +2327,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); @@ -2339,7 +2348,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); add_labeled_item(&pane, "A", false, cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 434975216a..3222ea2eb8 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -793,7 +793,7 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; - let workspace = requesting_window_id + let window = requesting_window_id .and_then(|window_id| { cx.update(|cx| { cx.replace_root_view(window_id, |cx| { @@ -852,9 +852,9 @@ impl Workspace { ) }, ) - .1 }); + let workspace = window.root(&cx); (app_state.initialize_workspace)( workspace.downgrade(), serialized_workspace.is_some(), @@ -864,7 +864,7 @@ impl Workspace { .await .log_err(); - cx.update_window(workspace.window_id(), |cx| cx.activate_window()); + window.update(&mut cx, |cx| cx.activate_window()); let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -3977,7 +3977,7 @@ pub fn join_remote_project( .await?; let window_bounds_override = window_bounds_env_override(&cx); - let (_, workspace) = cx.add_window( + let window = cx.add_window( (app_state.build_window_options)( window_bounds_override, None, @@ -3985,6 +3985,7 @@ pub fn join_remote_project( ), |cx| Workspace::new(0, project, app_state.clone(), cx), ); + let workspace = window.root(&cx); (app_state.initialize_workspace)( workspace.downgrade(), false, @@ -4113,10 +4114,11 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); // Adding an item with no ambiguity renders the tab without detail. - let item1 = cx.add_view(window_id, |_| { + let item1 = window.add_view(cx, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); item @@ -4128,7 +4130,7 @@ mod tests { // Adding an item that creates ambiguity increases the level of detail on // both tabs. - let item2 = cx.add_view(window_id, |_| { + let item2 = window.add_view(cx, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); item @@ -4142,7 +4144,7 @@ mod tests { // Adding an item that creates ambiguity increases the level of detail only // on the ambiguous tabs. In this case, the ambiguity can't be resolved so // we stop at the highest detail available. - let item3 = cx.add_view(window_id, |_| { + let item3 = window.add_view(cx, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); item @@ -4177,16 +4179,17 @@ mod tests { .await; let project = Project::test(fs, ["root1".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); - let item1 = cx.add_view(window_id, |cx| { + let item1 = window.add_view(cx, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) }); - let item2 = cx.add_view(window_id, |cx| { + let item2 = window.add_view(cx, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) }); @@ -4201,14 +4204,14 @@ mod tests { ); }); assert_eq!( - cx.current_window_title(window_id).as_deref(), + cx.current_window_title(window.id()).as_deref(), Some("one.txt — root1") ); // Add a second item to a non-empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); assert_eq!( - cx.current_window_title(window_id).as_deref(), + cx.current_window_title(window.id()).as_deref(), Some("two.txt — root1") ); project.read_with(cx, |project, cx| { @@ -4227,7 +4230,7 @@ mod tests { .await .unwrap(); assert_eq!( - cx.current_window_title(window_id).as_deref(), + cx.current_window_title(window.id()).as_deref(), Some("one.txt — root1") ); project.read_with(cx, |project, cx| { @@ -4247,14 +4250,14 @@ mod tests { .await .unwrap(); assert_eq!( - cx.current_window_title(window_id).as_deref(), + cx.current_window_title(window.id()).as_deref(), Some("one.txt — root1, root2") ); // Remove a project folder project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); assert_eq!( - cx.current_window_title(window_id).as_deref(), + cx.current_window_title(window.id()).as_deref(), Some("one.txt — root2") ); } @@ -4267,18 +4270,19 @@ mod tests { fs.insert_tree("/root", json!({ "one": "" })).await; let project = Project::test(fs, ["root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); // When there are no dirty items, there's nothing to do. - let item1 = cx.add_view(window_id, |_| TestItem::new()); + let item1 = window.add_view(cx, |_| TestItem::new()); workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); assert!(task.await.unwrap()); // When there are dirty untitled items, prompt to save each one. If the user // cancels any prompt, then abort. - let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true)); - let item3 = cx.add_view(window_id, |cx| { + let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true)); + let item3 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) @@ -4289,9 +4293,9 @@ mod tests { }); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); cx.foreground().run_until_parked(); - cx.simulate_prompt_answer(window_id, 2 /* cancel */); + cx.simulate_prompt_answer(window.id(), 2 /* cancel */); cx.foreground().run_until_parked(); - assert!(!cx.has_pending_prompt(window_id)); + assert!(!cx.has_pending_prompt(window.id())); assert!(!task.await.unwrap()); } @@ -4302,26 +4306,27 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); - let item1 = cx.add_view(window_id, |cx| { + let item1 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); - let item2 = cx.add_view(window_id, |cx| { + let item2 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) }); - let item3 = cx.add_view(window_id, |cx| { + let item3 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) }); - let item4 = cx.add_view(window_id, |cx| { + let item4 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_project_items(&[TestProjectItem::new_untitled(cx)]) @@ -4349,10 +4354,10 @@ mod tests { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); - assert!(cx.has_pending_prompt(window_id)); + assert!(cx.has_pending_prompt(window.id())); // Confirm saving item 1. - cx.simulate_prompt_answer(window_id, 0); + cx.simulate_prompt_answer(window.id(), 0); cx.foreground().run_until_parked(); // Item 1 is saved. There's a prompt to save item 3. @@ -4363,10 +4368,10 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); - assert!(cx.has_pending_prompt(window_id)); + assert!(cx.has_pending_prompt(window.id())); // Cancel saving item 3. - cx.simulate_prompt_answer(window_id, 1); + cx.simulate_prompt_answer(window.id(), 1); cx.foreground().run_until_parked(); // Item 3 is reloaded. There's a prompt to save item 4. @@ -4377,10 +4382,10 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); - assert!(cx.has_pending_prompt(window_id)); + assert!(cx.has_pending_prompt(window.id())); // Confirm saving item 4. - cx.simulate_prompt_answer(window_id, 0); + cx.simulate_prompt_answer(window.id(), 0); cx.foreground().run_until_parked(); // There's a prompt for a path for item 4. @@ -4404,13 +4409,14 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); // Create several workspace items with single project entries, and two // workspace items with multiple project entries. let single_entry_items = (0..=4) .map(|project_entry_id| { - cx.add_view(window_id, |cx| { + window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_project_items(&[TestProjectItem::new( @@ -4421,7 +4427,7 @@ mod tests { }) }) .collect::>(); - let item_2_3 = cx.add_view(window_id, |cx| { + let item_2_3 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) @@ -4430,7 +4436,7 @@ mod tests { single_entry_items[3].read(cx).project_items[0].clone(), ]) }); - let item_3_4 = cx.add_view(window_id, |cx| { + let item_3_4 = window.add_view(cx, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) @@ -4482,7 +4488,7 @@ mod tests { &[ProjectEntryId::from_proto(0)] ); }); - cx.simulate_prompt_answer(window_id, 0); + cx.simulate_prompt_answer(window.id(), 0); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { @@ -4491,7 +4497,7 @@ mod tests { &[ProjectEntryId::from_proto(2)] ); }); - cx.simulate_prompt_answer(window_id, 0); + cx.simulate_prompt_answer(window.id(), 0); cx.foreground().run_until_parked(); close.await.unwrap(); @@ -4507,10 +4513,11 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - let item = cx.add_view(window_id, |cx| { + let item = window.add_view(cx, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item_id = item.id(); @@ -4550,7 +4557,7 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); // Deactivating the window still saves the file. - cx.simulate_window_activation(Some(window_id)); + cx.simulate_window_activation(Some(window.id())); item.update(cx, |item, cx| { cx.focus_self(); item.is_dirty = true; @@ -4592,7 +4599,7 @@ mod tests { pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) .await .unwrap(); - assert!(!cx.has_pending_prompt(window_id)); + assert!(!cx.has_pending_prompt(window.id())); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. @@ -4613,7 +4620,7 @@ mod tests { let _close_items = pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); deterministic.run_until_parked(); - assert!(cx.has_pending_prompt(window_id)); + assert!(cx.has_pending_prompt(window.id())); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); } @@ -4624,9 +4631,10 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); - let item = cx.add_view(window_id, |cx| { + let item = window.add_view(cx, |cx| { TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); @@ -4677,7 +4685,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); let panel = workspace.update(cx, |workspace, cx| { let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right)); @@ -4824,7 +4833,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // Add panel_1 on the left, panel_2 on the right. @@ -4979,7 +4989,7 @@ mod tests { // If focus is transferred to another view that's not a panel or another pane, we still show // the panel as zoomed. - let focus_receiver = cx.add_view(window_id, |_| EmptyView); + let focus_receiver = window.add_view(cx, |_| EmptyView); focus_receiver.update(cx, |_, cx| cx.focus_self()); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4b0bf1cd4c..1770c5648e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -983,7 +983,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1295,7 +1297,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); // Open a file within an existing worktree. workspace @@ -1336,7 +1340,9 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer @@ -1429,7 +1435,9 @@ mod tests { let project = Project::test(app_state.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); // Create a new untitled buffer cx.dispatch_action(window_id, NewFile); @@ -1480,7 +1488,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + let window_id = window.id(); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1554,7 +1564,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .detach(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); @@ -1831,7 +1843,9 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = cx + .add_window(|cx| Workspace::test_new(project, cx)) + .detach(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); @@ -2073,7 +2087,8 @@ mod tests { cx.foreground().run_until_parked(); - let (window_id, _view) = cx.add_window(|_| TestView); + let window = cx.add_window(|_| TestView); + let window_id = window.id(); // Test loading the keymap base at all assert_key_bindings_for( @@ -2243,7 +2258,8 @@ mod tests { cx.foreground().run_until_parked(); - let (window_id, _view) = cx.add_window(|_| TestView); + let window = cx.add_window(|_| TestView); + let window_id = window.id(); // Test loading the keymap base at all assert_key_bindings_for( From 8e36da1382e84c87c5d3576a848eb06645bb21ab Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 2 Aug 2023 15:02:55 -0600 Subject: [PATCH 5/9] Get tests passing --- crates/gpui/src/app.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 45169ed3af..bd615522c2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3831,7 +3831,14 @@ impl WindowHandle { // TODO: Implement window dropping behavior when we don't call this. pub fn detach(mut self, cx: &impl BorrowAppContext) -> ViewHandle { let root = self.root(cx); - self.any_handle.ref_counts.take(); + let ref_counts = self.any_handle.ref_counts.take(); + #[cfg(any(test, feature = "test-support"))] + ref_counts + .unwrap() + .lock() + .leak_detector + .lock() + .handle_dropped(self.id(), self.any_handle.handle_id); root } From 3c938a7377bbc2a3147f71da53ef05a1f45eaa5a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 3 Aug 2023 08:10:16 -0600 Subject: [PATCH 6/9] WIP --- crates/gpui/src/app.rs | 16 ++++++++++++---- crates/gpui/src/app/test_app_context.rs | 2 ++ crates/gpui/src/app/window.rs | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bd615522c2..9b847e9c0c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -130,10 +130,12 @@ pub trait BorrowAppContext { } pub trait BorrowWindowContext { - fn read_with(&self, window_id: usize, f: F) -> T + type Return; + + fn read_with(&self, window_id: usize, f: F) -> Self::Return where F: FnOnce(&WindowContext) -> T; - fn update(&mut self, window_id: usize, f: F) -> T + fn update(&mut self, window_id: usize, f: F) -> Self::Return where F: FnOnce(&mut WindowContext) -> T; } @@ -3358,6 +3360,8 @@ impl BorrowAppContext for ViewContext<'_, '_, V> { } impl BorrowWindowContext for ViewContext<'_, '_, V> { + type Return = T; + fn read_with T>(&self, window_id: usize, f: F) -> T { BorrowWindowContext::read_with(&*self.window_context, window_id, f) } @@ -3463,6 +3467,8 @@ impl BorrowAppContext for LayoutContext<'_, '_, '_, V> { } impl BorrowWindowContext for LayoutContext<'_, '_, '_, V> { + type Return = T; + fn read_with T>(&self, window_id: usize, f: F) -> T { BorrowWindowContext::read_with(&*self.view_context, window_id, f) } @@ -3515,6 +3521,8 @@ impl BorrowAppContext for EventContext<'_, '_, '_, V> { } impl BorrowWindowContext for EventContext<'_, '_, '_, V> { + type Return = T; + fn read_with T>(&self, window_id: usize, f: F) -> T { BorrowWindowContext::read_with(&*self.view_context, window_id, f) } @@ -4013,7 +4021,7 @@ impl ViewHandle { cx.read_view(self) } - pub fn read_with(&self, cx: &C, read: F) -> S + pub fn read_with(&self, cx: &C, read: F) -> C::Return where C: BorrowWindowContext, F: FnOnce(&T, &ViewContext) -> S, @@ -4024,7 +4032,7 @@ impl ViewHandle { }) } - pub fn update(&self, cx: &mut C, update: F) -> S + pub fn update(&self, cx: &mut C, update: F) -> C::Return where C: BorrowWindowContext, F: FnOnce(&mut T, &mut ViewContext) -> S, diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 80f1037466..5c7947a448 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -406,6 +406,8 @@ impl BorrowAppContext for TestAppContext { } impl BorrowWindowContext for TestAppContext { + type Return = T; + fn read_with T>(&self, window_id: usize, f: F) -> T { self.cx .borrow() diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 7cdcbc2c8f..671d2b38c7 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -142,6 +142,8 @@ impl BorrowAppContext for WindowContext<'_> { } impl BorrowWindowContext for WindowContext<'_> { + type Return = T; + fn read_with T>(&self, window_id: usize, f: F) -> T { if self.window_id == window_id { f(self) From afcc0d621b8524d4b3cfa9a5ed19b00c11666348 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 3 Aug 2023 17:03:39 -0600 Subject: [PATCH 7/9] WIP --- crates/editor/src/editor_tests.rs | 98 ++++---- crates/editor/src/element.rs | 6 +- crates/editor/src/inlay_hint_cache.rs | 14 +- crates/file_finder/src/file_finder.rs | 20 +- crates/gpui/src/app.rs | 278 ++++++++++++--------- crates/gpui/src/app/test_app_context.rs | 10 +- crates/gpui/src/app/window.rs | 10 +- crates/language_tools/src/lsp_log_tests.rs | 2 +- crates/project_panel/src/project_panel.rs | 8 +- crates/search/src/project_search.rs | 4 +- crates/terminal_view/src/terminal_view.rs | 2 +- crates/workspace/src/workspace.rs | 104 ++++---- crates/zed/src/zed.rs | 6 +- 13 files changed, 300 insertions(+), 262 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 96921643d4..a114cd437b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -64,7 +64,7 @@ fn test_edit_events(cx: &mut TestAppContext) { Editor::for_buffer(buffer.clone(), None, cx) } }) - .detach(cx); + .root(cx); let editor2 = cx .add_window({ let events = events.clone(); @@ -81,7 +81,7 @@ fn test_edit_events(cx: &mut TestAppContext) { Editor::for_buffer(buffer.clone(), None, cx) } }) - .detach(cx); + .root(cx); assert_eq!(mem::take(&mut *events.borrow_mut()), []); // Mutating editor 1 will emit an `Edited` event only for that editor. @@ -179,7 +179,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx .add_window(|cx| build_editor(buffer.clone(), cx)) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); @@ -354,7 +354,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); }); @@ -423,7 +423,7 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); @@ -471,7 +471,7 @@ fn test_clone(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&text, cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone())); @@ -489,7 +489,7 @@ fn test_clone(cx: &mut TestAppContext) { .update(cx, |editor, cx| { cx.add_window(Default::default(), |cx| editor.clone(cx)) }) - .detach(cx); + .root(cx); let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)); let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)); @@ -639,7 +639,7 @@ fn test_cancel(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); @@ -704,7 +704,7 @@ fn test_fold_action(cx: &mut TestAppContext) { ); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -774,7 +774,7 @@ fn test_move_cursor(cx: &mut TestAppContext) { let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx)); let view = cx .add_window(|cx| build_editor(buffer.clone(), cx)) - .detach(cx); + .root(cx); buffer.update(cx, |buffer, cx| { buffer.edit( @@ -854,7 +854,7 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); assert_eq!('ⓐ'.len_utf8(), 3); assert_eq!('α'.len_utf8(), 2); @@ -961,7 +961,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); @@ -1013,7 +1013,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\n def", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -1178,7 +1178,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -1233,7 +1233,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.set_wrap_width(Some(140.), cx); @@ -1568,7 +1568,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("one two three four", cx); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -1606,7 +1606,7 @@ fn test_newline(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -1651,7 +1651,7 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { }); editor }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { // Edit the buffer directly, deleting ranges surrounding the editor's selections @@ -1863,7 +1863,7 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20])); editor }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { // Edit the buffer directly, deleting ranges surrounding the editor's selections @@ -2375,7 +2375,7 @@ fn test_delete_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2400,7 +2400,7 @@ fn test_delete_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)]) @@ -2704,7 +2704,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2732,7 +2732,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -2761,7 +2761,7 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -2862,7 +2862,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { let snapshot = editor.buffer.read(cx).snapshot(cx); editor.insert_blocks( @@ -3182,7 +3182,7 @@ fn test_select_all(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); assert_eq!( @@ -3201,7 +3201,7 @@ fn test_select_line(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { s.select_display_ranges([ @@ -3250,7 +3250,7 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -3323,7 +3323,7 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); build_editor(buffer, cx) }) - .detach(cx); + .root(cx); view.update(cx, |view, cx| { view.change_selections(None, cx, |s| { @@ -3608,7 +3608,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -3771,7 +3771,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx)) .await; @@ -4334,7 +4334,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -4482,7 +4482,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -4572,7 +4572,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) { ); let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor.update(cx, |editor, cx| { let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); @@ -4702,7 +4702,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); assert!(cx.read(|cx| editor.is_dirty(cx))); @@ -4814,7 +4814,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); assert!(cx.read(|cx| editor.is_dirty(cx))); @@ -4928,7 +4928,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let editor = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); let format = editor.update(cx, |editor, cx| { @@ -5706,7 +5706,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { multibuffer }); - let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); + let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); view.update(cx, |view, cx| { assert_eq!(view.text(cx), "aaaa\nbbbb"); view.change_selections(None, cx, |s| { @@ -5776,7 +5776,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { multibuffer }); - let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); + let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); view.update(cx, |view, cx| { let (expected_text, selection_ranges) = marked_text_ranges( indoc! {" @@ -5869,7 +5869,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { ); editor }) - .detach(cx); + .root(cx); // Refreshing selections is a no-op when excerpts haven't changed. editor.update(cx, |editor, cx| { @@ -5950,7 +5950,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { ); editor }) - .detach(cx); + .root(cx); multibuffer.update(cx, |multibuffer, cx| { multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); @@ -6013,7 +6013,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let view = cx.add_window(|cx| build_editor(buffer, cx)).detach(cx); + let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) .await; @@ -6054,7 +6054,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); build_editor(buffer.clone(), cx) }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { struct Type1; @@ -6145,7 +6145,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { }); let leader = cx .add_window(|cx| build_editor(buffer.clone(), cx)) - .detach(cx); + .root(cx); let follower = cx .update(|cx| { cx.add_window( @@ -6156,7 +6156,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { |cx| build_editor(buffer.clone(), cx), ) }) - .detach(cx); + .root(cx); let is_still_following = Rc::new(RefCell::new(true)); let follower_edit_event_count = Rc::new(RefCell::new(0)); @@ -6289,7 +6289,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let leader = pane.update(cx, |_, cx| { @@ -7033,7 +7033,7 @@ async fn test_copilot_multibuffer( ); multibuffer }); - let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); handle_copilot_completion_request( &copilot_lsp, @@ -7163,7 +7163,7 @@ async fn test_copilot_disabled_globs( ); multibuffer }); - let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx); + let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); let mut copilot_requests = copilot_lsp .handle_request::(move |_params, _cx| async move { @@ -7244,7 +7244,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) { project.update(cx, |project, _| project.languages().add(Arc::new(language))); let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dc40e7fb85..2d4b273f5e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3007,7 +3007,7 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); Editor::new(EditorMode::Full, buffer, None, None, cx) }) - .detach(cx); + .root(cx); let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let layouts = editor.update(cx, |editor, cx| { @@ -3028,7 +3028,7 @@ mod tests { let buffer = MultiBuffer::build_simple("", cx); Editor::new(EditorMode::Full, buffer, None, None, cx) }) - .detach(cx); + .root(cx); editor.update(cx, |editor, cx| { editor.set_placeholder_text("hello", cx); @@ -3240,7 +3240,7 @@ mod tests { let buffer = MultiBuffer::build_simple(&input_text, cx); Editor::new(editor_mode, buffer, None, None, cx) }) - .detach(cx); + .root(cx); let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (_, layout_state) = editor.update(cx, |editor, cx| { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 089cbb2995..47a27c049f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1138,7 +1138,7 @@ mod tests { let project = Project::test(fs, ["/a".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -1840,7 +1840,7 @@ mod tests { project.update(cx, |project, _| project.languages().add(Arc::new(language))); let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -1995,7 +1995,7 @@ mod tests { }); let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -2083,7 +2083,7 @@ mod tests { cx.foreground().run_until_parked(); let editor = cx .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) - .detach(cx); + .root(cx); let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); let closure_editor_edited = Arc::clone(&editor_edited); @@ -2337,7 +2337,7 @@ all hints should be invalidated and requeried for all of its visible excerpts" }); let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() @@ -2384,7 +2384,7 @@ all hints should be invalidated and requeried for all of its visible excerpts" cx.foreground().run_until_parked(); let editor = cx .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) - .detach(cx); + .root(cx); let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); let closure_editor_edited = Arc::clone(&editor_edited); @@ -2574,7 +2574,7 @@ all hints should be invalidated and requeried for all of its visible excerpts" project.update(cx, |project, _| project.languages().add(Arc::new(language))); let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 2c9d9c0c71..12bf324262 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -842,7 +842,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let finder = cx .add_window(|cx| { Picker::new( @@ -856,7 +856,7 @@ mod tests { cx, ) }) - .detach(cx); + .root(cx); let query = test_path_like("hi"); finder @@ -940,7 +940,7 @@ mod tests { .await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let finder = cx .add_window(|cx| { Picker::new( @@ -954,7 +954,7 @@ mod tests { cx, ) }) - .detach(cx); + .root(cx); finder .update(cx, |f, cx| { f.delegate_mut().spawn_search(test_path_like("hi"), cx) @@ -980,7 +980,7 @@ mod tests { .await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let finder = cx .add_window(|cx| { Picker::new( @@ -994,7 +994,7 @@ mod tests { cx, ) }) - .detach(cx); + .root(cx); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -1051,7 +1051,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -1078,7 +1078,7 @@ mod tests { cx, ) }) - .detach(cx); + .root(cx); finder .update(cx, |f, cx| { @@ -1117,7 +1117,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let finder = cx .add_window(|cx| { Picker::new( @@ -1131,7 +1131,7 @@ mod tests { cx, ) }) - .detach(cx); + .root(cx); finder .update(cx, |f, cx| { f.delegate_mut().spawn_search(test_path_like("dir"), cx) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9b847e9c0c..dce0b0e5f0 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -130,12 +130,12 @@ pub trait BorrowAppContext { } pub trait BorrowWindowContext { - type Return; + type Result; - fn read_with(&self, window_id: usize, f: F) -> Self::Return + fn read_window_with(&self, window_id: usize, f: F) -> Self::Result where F: FnOnce(&WindowContext) -> T; - fn update(&mut self, window_id: usize, f: F) -> Self::Return + fn update_window(&mut self, window_id: usize, f: F) -> Self::Result where F: FnOnce(&mut WindowContext) -> T; } @@ -458,6 +458,26 @@ impl BorrowAppContext for AsyncAppContext { } } +impl BorrowWindowContext for AsyncAppContext { + type Result = Option; + + fn read_window_with(&self, window_id: usize, f: F) -> Self::Result + where + F: FnOnce(&WindowContext) -> T, + { + self.0.borrow().read_with(|cx| cx.read_window(window_id, f)) + } + + fn update_window(&mut self, window_id: usize, f: F) -> Self::Result + where + F: FnOnce(&mut WindowContext) -> T, + { + self.0 + .borrow_mut() + .update(|cx| cx.update_window(window_id, f)) + } +} + type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize); type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext); @@ -2162,6 +2182,24 @@ impl BorrowAppContext for AppContext { } } +impl BorrowWindowContext for AppContext { + type Result = Option; + + fn read_window_with(&self, window_id: usize, f: F) -> Self::Result + where + F: FnOnce(&WindowContext) -> T, + { + AppContext::read_window(self, window_id, f) + } + + fn update_window(&mut self, window_id: usize, f: F) -> Self::Result + where + F: FnOnce(&mut WindowContext) -> T, + { + AppContext::update_window(self, window_id, f) + } +} + #[derive(Debug)] pub enum ParentId { View(usize), @@ -3360,14 +3398,18 @@ impl BorrowAppContext for ViewContext<'_, '_, V> { } impl BorrowWindowContext for ViewContext<'_, '_, V> { - type Return = T; + type Result = T; - fn read_with T>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(&*self.window_context, window_id, f) + fn read_window_with T>(&self, window_id: usize, f: F) -> T { + BorrowWindowContext::read_window_with(&*self.window_context, window_id, f) } - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(&mut *self.window_context, window_id, f) + fn update_window T>( + &mut self, + window_id: usize, + f: F, + ) -> T { + BorrowWindowContext::update_window(&mut *self.window_context, window_id, f) } } @@ -3467,14 +3509,18 @@ impl BorrowAppContext for LayoutContext<'_, '_, '_, V> { } impl BorrowWindowContext for LayoutContext<'_, '_, '_, V> { - type Return = T; + type Result = T; - fn read_with T>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(&*self.view_context, window_id, f) + fn read_window_with T>(&self, window_id: usize, f: F) -> T { + BorrowWindowContext::read_window_with(&*self.view_context, window_id, f) } - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(&mut *self.view_context, window_id, f) + fn update_window T>( + &mut self, + window_id: usize, + f: F, + ) -> T { + BorrowWindowContext::update_window(&mut *self.view_context, window_id, f) } } @@ -3521,14 +3567,18 @@ impl BorrowAppContext for EventContext<'_, '_, '_, V> { } impl BorrowWindowContext for EventContext<'_, '_, '_, V> { - type Return = T; + type Result = T; - fn read_with T>(&self, window_id: usize, f: F) -> T { - BorrowWindowContext::read_with(&*self.view_context, window_id, f) + fn read_window_with T>(&self, window_id: usize, f: F) -> T { + BorrowWindowContext::read_window_with(&*self.view_context, window_id, f) } - fn update T>(&mut self, window_id: usize, f: F) -> T { - BorrowWindowContext::update(&mut *self.view_context, window_id, f) + fn update_window T>( + &mut self, + window_id: usize, + f: F, + ) -> T { + BorrowWindowContext::update_window(&mut *self.view_context, window_id, f) } } @@ -3830,32 +3880,16 @@ impl WindowHandle { self.any_handle.id() } - pub fn root(&self, cx: &impl BorrowAppContext) -> ViewHandle { + pub fn root(&self, cx: &C) -> C::Result> { self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap()) } - /// Keep this window open until it's explicitly closed. - // - // TODO: Implement window dropping behavior when we don't call this. - pub fn detach(mut self, cx: &impl BorrowAppContext) -> ViewHandle { - let root = self.root(cx); - let ref_counts = self.any_handle.ref_counts.take(); - #[cfg(any(test, feature = "test-support"))] - ref_counts - .unwrap() - .lock() - .leak_detector - .lock() - .handle_dropped(self.id(), self.any_handle.handle_id); - root - } - - pub fn read_with(&self, cx: &C, read: F) -> R + pub fn read_with(&self, cx: &C, read: F) -> C::Result where - C: BorrowAppContext, + C: BorrowWindowContext, F: FnOnce(&WindowContext) -> R, { - cx.read_with(|cx| cx.read_window(self.id(), read).unwrap()) + cx.read_window_with(self.id(), |cx| read(cx)) } pub fn update(&self, cx: &mut C, update: F) -> R @@ -3891,9 +3925,9 @@ impl WindowHandle { root_view.read(cx) } - pub fn read_root_with(&self, cx: &C, read: F) -> R + pub fn read_root_with(&self, cx: &C, read: F) -> C::Result where - C: BorrowAppContext, + C: BorrowWindowContext, F: FnOnce(&V, &ViewContext) -> R, { self.read_with(cx, |cx| { @@ -4021,25 +4055,25 @@ impl ViewHandle { cx.read_view(self) } - pub fn read_with(&self, cx: &C, read: F) -> C::Return + pub fn read_with(&self, cx: &C, read: F) -> C::Result where C: BorrowWindowContext, F: FnOnce(&T, &ViewContext) -> S, { - cx.read_with(self.window_id, |cx| { + cx.read_window_with(self.window_id, |cx| { let cx = ViewContext::immutable(cx, self.view_id); read(cx.read_view(self), &cx) }) } - pub fn update(&self, cx: &mut C, update: F) -> C::Return + pub fn update(&self, cx: &mut C, update: F) -> C::Result where C: BorrowWindowContext, F: FnOnce(&mut T, &mut ViewContext) -> S, { let mut update = Some(update); - cx.update(self.window_id, |cx| { + cx.update_window(self.window_id, |cx| { cx.update_view(self, &mut |view, cx| { let update = update.take().unwrap(); update(view, cx) @@ -5005,7 +5039,7 @@ mod tests { } #[crate::test(self)] - fn test_entity_release_hooks(cx: &mut AppContext) { + fn test_entity_release_hooks(cx: &mut TestAppContext) { struct Model { released: Rc>, } @@ -5048,7 +5082,7 @@ mod tests { let model = cx.add_model(|_| Model { released: model_released.clone(), }); - let window = cx.add_window(Default::default(), |_| View { + let window = cx.add_window(|_| View { released: view_released.clone(), }); let view = window.root(cx); @@ -5056,16 +5090,18 @@ mod tests { assert!(!model_released.get()); assert!(!view_released.get()); - cx.observe_release(&model, { - let model_release_observed = model_release_observed.clone(); - move |_, _| model_release_observed.set(true) - }) - .detach(); - cx.observe_release(&view, { - let view_release_observed = view_release_observed.clone(); - move |_, _| view_release_observed.set(true) - }) - .detach(); + cx.update(|cx| { + cx.observe_release(&model, { + let model_release_observed = model_release_observed.clone(); + move |_, _| model_release_observed.set(true) + }) + .detach(); + cx.observe_release(&view, { + let view_release_observed = view_release_observed.clone(); + move |_, _| view_release_observed.set(true) + }) + .detach(); + }); cx.update(move |_| { drop(model); @@ -5795,7 +5831,7 @@ mod tests { } #[crate::test(self)] - fn test_dispatch_action(cx: &mut AppContext) { + fn test_dispatch_action(cx: &mut TestAppContext) { struct ViewA { id: usize, child: Option, @@ -5846,68 +5882,70 @@ mod tests { impl_actions!(test, [Action]); let actions = Rc::new(RefCell::new(Vec::new())); - - cx.add_global_action({ - let actions = actions.clone(); - move |_: &Action, _: &mut AppContext| { - actions.borrow_mut().push("global".to_string()); - } - }); - - cx.add_action({ - let actions = actions.clone(); - move |view: &mut ViewA, action: &Action, cx| { - assert_eq!(action.0, "bar"); - cx.propagate_action(); - actions.borrow_mut().push(format!("{} a", view.id)); - } - }); - - cx.add_action({ - let actions = actions.clone(); - move |view: &mut ViewA, _: &Action, cx| { - if view.id != 1 { - cx.add_view(|cx| { - cx.propagate_action(); // Still works on a nested ViewContext - ViewB { id: 5, child: None } - }); - } - actions.borrow_mut().push(format!("{} b", view.id)); - } - }); - - cx.add_action({ - let actions = actions.clone(); - move |view: &mut ViewB, _: &Action, cx| { - cx.propagate_action(); - actions.borrow_mut().push(format!("{} c", view.id)); - } - }); - - cx.add_action({ - let actions = actions.clone(); - move |view: &mut ViewB, _: &Action, cx| { - cx.propagate_action(); - actions.borrow_mut().push(format!("{} d", view.id)); - } - }); - - cx.capture_action({ - let actions = actions.clone(); - move |view: &mut ViewA, _: &Action, cx| { - cx.propagate_action(); - actions.borrow_mut().push(format!("{} capture", view.id)); - } - }); - let observed_actions = Rc::new(RefCell::new(Vec::new())); - cx.observe_actions({ - let observed_actions = observed_actions.clone(); - move |action_id, _| observed_actions.borrow_mut().push(action_id) - }) - .detach(); - let window = cx.add_window(Default::default(), |_| ViewA { id: 1, child: None }); + cx.update(|cx| { + cx.add_global_action({ + let actions = actions.clone(); + move |_: &Action, _: &mut AppContext| { + actions.borrow_mut().push("global".to_string()); + } + }); + + cx.add_action({ + let actions = actions.clone(); + move |view: &mut ViewA, action: &Action, cx| { + assert_eq!(action.0, "bar"); + cx.propagate_action(); + actions.borrow_mut().push(format!("{} a", view.id)); + } + }); + + cx.add_action({ + let actions = actions.clone(); + move |view: &mut ViewA, _: &Action, cx| { + if view.id != 1 { + cx.add_view(|cx| { + cx.propagate_action(); // Still works on a nested ViewContext + ViewB { id: 5, child: None } + }); + } + actions.borrow_mut().push(format!("{} b", view.id)); + } + }); + + cx.add_action({ + let actions = actions.clone(); + move |view: &mut ViewB, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} c", view.id)); + } + }); + + cx.add_action({ + let actions = actions.clone(); + move |view: &mut ViewB, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} d", view.id)); + } + }); + + cx.capture_action({ + let actions = actions.clone(); + move |view: &mut ViewA, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} capture", view.id)); + } + }); + + cx.observe_actions({ + let observed_actions = observed_actions.clone(); + move |action_id, _| observed_actions.borrow_mut().push(action_id) + }) + .detach(); + }); + + let window = cx.add_window(|_| ViewA { id: 1, child: None }); let view_1 = window.root(cx); let view_2 = window.update(cx, |cx| { let child = cx.add_view(|_| ViewB { id: 2, child: None }); @@ -5956,7 +5994,7 @@ mod tests { // Remove view_1, which doesn't propagate the action - let window = cx.add_window(Default::default(), |_| ViewB { id: 2, child: None }); + let window = cx.add_window(|_| ViewB { id: 2, child: None }); let view_2 = window.root(cx); let view_3 = window.update(cx, |cx| { let child = cx.add_view(|_| ViewA { id: 3, child: None }); @@ -6457,7 +6495,7 @@ mod tests { } #[crate::test(self)] - fn test_refresh_windows(cx: &mut AppContext) { + fn test_refresh_windows(cx: &mut TestAppContext) { struct View(usize); impl super::Entity for View { @@ -6474,7 +6512,7 @@ mod tests { } } - let window = cx.add_window(Default::default(), |_| View(0)); + let window = cx.add_window(|_| View(0)); let root_view = window.root(cx); window.update(cx, |cx| { assert_eq!( diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 5c7947a448..0165c52e9f 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -406,16 +406,20 @@ impl BorrowAppContext for TestAppContext { } impl BorrowWindowContext for TestAppContext { - type Return = T; + type Result = T; - fn read_with T>(&self, window_id: usize, f: F) -> T { + fn read_window_with T>(&self, window_id: usize, f: F) -> T { self.cx .borrow() .read_window(window_id, f) .expect("window was closed") } - fn update T>(&mut self, window_id: usize, f: F) -> T { + fn update_window T>( + &mut self, + window_id: usize, + f: F, + ) -> T { self.cx .borrow_mut() .update_window(window_id, f) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 671d2b38c7..0149c310da 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -142,9 +142,9 @@ impl BorrowAppContext for WindowContext<'_> { } impl BorrowWindowContext for WindowContext<'_> { - type Return = T; + type Result = T; - fn read_with T>(&self, window_id: usize, f: F) -> T { + fn read_window_with T>(&self, window_id: usize, f: F) -> T { if self.window_id == window_id { f(self) } else { @@ -152,7 +152,11 @@ impl BorrowWindowContext for WindowContext<'_> { } } - fn update T>(&mut self, window_id: usize, f: F) -> T { + fn update_window T>( + &mut self, + window_id: usize, + f: F, + ) -> T { if self.window_id == window_id { f(self) } else { diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index ce05a417ad..d26000ebc7 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/crates/language_tools/src/lsp_log_tests.rs @@ -63,7 +63,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) { let log_view = cx .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx)) - .detach(cx); + .root(cx); language_server.notify::(lsp::LogMessageParams { message: "hello from the server".into(), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index fdc5ea108a..021ea2d3bc 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1782,7 +1782,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..50, cx), @@ -2327,7 +2327,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); panel.update(cx, |panel, cx| { @@ -2641,7 +2641,7 @@ mod tests { let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); let new_search_events_count = Arc::new(AtomicUsize::new(0)); @@ -2730,7 +2730,7 @@ mod tests { let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); panel.update(cx, |panel, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e57edd3b14..0db66b4e37 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1449,7 +1449,7 @@ pub mod tests { let search = cx.add_model(|cx| ProjectSearch::new(project, cx)); let search_view = cx .add_window(|cx| ProjectSearchView::new(search.clone(), cx)) - .detach(cx); + .root(cx); search_view.update(cx, |search_view, cx| { search_view @@ -1754,7 +1754,7 @@ pub mod tests { }); let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let active_item = cx.read(|cx| { workspace diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 874978b4fc..a600046ac2 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1072,7 +1072,7 @@ mod tests { let project = Project::test(params.fs.clone(), [], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); (project, workspace) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3222ea2eb8..2efa9f8daa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -793,68 +793,60 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; - let window = requesting_window_id - .and_then(|window_id| { - cx.update(|cx| { - cx.replace_root_view(window_id, |cx| { - Workspace::new( - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ) - }) + let window = requesting_window_id.and_then(|window_id| { + cx.update(|cx| { + cx.replace_root_view(window_id, |cx| { + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }) }) - .unwrap_or_else(|| { - let window_bounds_override = window_bounds_env_override(&cx); - let (bounds, display) = if let Some(bounds) = window_bounds_override { - (Some(bounds), None) - } else { - serialized_workspace - .as_ref() - .and_then(|serialized_workspace| { - let display = serialized_workspace.display?; - let mut bounds = serialized_workspace.bounds?; + }); + let window = window.unwrap_or_else(|| { + let window_bounds_override = window_bounds_env_override(&cx); + let (bounds, display) = if let Some(bounds) = window_bounds_override { + (Some(bounds), None) + } else { + serialized_workspace + .as_ref() + .and_then(|serialized_workspace| { + let display = serialized_workspace.display?; + let mut bounds = serialized_workspace.bounds?; - // Stored bounds are relative to the containing display. - // So convert back to global coordinates if that screen still exists - if let WindowBounds::Fixed(mut window_bounds) = bounds { - if let Some(screen) = cx.platform().screen_by_id(display) { - let screen_bounds = screen.bounds(); - window_bounds.set_origin_x( - window_bounds.origin_x() + screen_bounds.origin_x(), - ); - window_bounds.set_origin_y( - window_bounds.origin_y() + screen_bounds.origin_y(), - ); - bounds = WindowBounds::Fixed(window_bounds); - } else { - // Screen no longer exists. Return none here. - return None; - } + // Stored bounds are relative to the containing display. + // So convert back to global coordinates if that screen still exists + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds.set_origin_x( + window_bounds.origin_x() + screen_bounds.origin_x(), + ); + window_bounds.set_origin_y( + window_bounds.origin_y() + screen_bounds.origin_y(), + ); + bounds = WindowBounds::Fixed(window_bounds); + } else { + // Screen no longer exists. Return none here. + return None; } + } - Some((bounds, display)) - }) - .unzip() - }; + Some((bounds, display)) + }) + .unzip() + }; - // Use the serialized workspace to construct the new window - cx.add_window( - (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| { - Workspace::new( - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ) - }, - ) - }); + // Use the serialized workspace to construct the new window + cx.add_window( + (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), + |cx| { + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + }, + ) + }); + + // We haven't yielded the main thread since obtaining the window handle, + // so the window exists. + let workspace = window.root(&cx).unwrap(); - let workspace = window.root(&cx); (app_state.initialize_workspace)( workspace.downgrade(), serialized_workspace.is_some(), @@ -3985,7 +3977,7 @@ pub fn join_remote_project( ), |cx| Workspace::new(0, project, app_state.clone(), cx), ); - let workspace = window.root(&cx); + let workspace = window.root(&cx).unwrap(); (app_state.initialize_workspace)( workspace.downgrade(), false, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1770c5648e..a459122cfc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -985,7 +985,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1566,7 +1566,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project.clone(), cx)) - .detach(cx); + .root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); @@ -1845,7 +1845,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let workspace = cx .add_window(|cx| Workspace::test_new(project, cx)) - .detach(cx); + .root(cx); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); From 485c0a482ee8e7b2a2014f1129ca060d4554af0c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 3 Aug 2023 17:11:47 -0600 Subject: [PATCH 8/9] Don't refcount window handles --- crates/collab/src/tests/integration_tests.rs | 4 +- .../src/incoming_call_notification.rs | 2 +- .../src/project_shared_notification.rs | 2 +- crates/command_palette/src/command_palette.rs | 2 +- crates/copilot/src/sign_in.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/editor_tests.rs | 2 +- .../src/test/editor_lsp_test_context.rs | 2 +- crates/editor/src/test/editor_test_context.rs | 2 +- crates/file_finder/src/file_finder.rs | 22 ++-- crates/gpui/src/app.rs | 100 ++++-------------- crates/gpui/src/app/ref_counts.rs | 22 ---- crates/gpui/src/app/test_app_context.rs | 4 +- crates/gpui/src/app/window.rs | 2 +- crates/project_panel/src/project_panel.rs | 8 +- crates/project_symbols/src/project_symbols.rs | 2 +- crates/search/src/buffer_search.rs | 10 +- crates/search/src/project_search.rs | 4 +- crates/workspace/src/workspace.rs | 36 +++---- crates/zed/src/zed.rs | 12 +-- 20 files changed, 83 insertions(+), 163 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 1a8e6d938d..037f97f71a 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -3446,7 +3446,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); let mut editor_cx_a = EditorTestContext { cx: cx_a, - window_id: window_a.id(), + window_id: window_a.window_id(), editor: editor_a, }; @@ -3459,7 +3459,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); let mut editor_cx_b = EditorTestContext { cx: cx_b, - window_id: window_b.id(), + window_id: window_b.window_id(), editor: editor_b, }; diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index a9c5e697a5..770f5d5795 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -49,7 +49,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { |_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()), ); - notification_windows.push(window.id()); + notification_windows.push(window.window_id()); } } } diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 03ab91623b..5be7e33bf3 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -52,7 +52,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { notification_windows .entry(*project_id) .or_insert(Vec::new()) - .push(window.id()); + .push(window.window_id()); } } room::Event::RemoteProjectUnshared { project_id } => { diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 7d4b4126b7..935358c2a1 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -297,7 +297,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), [], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let editor = cx.add_view(window_id, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 659bee7445..0d5bb28ed6 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -25,7 +25,7 @@ pub fn init(cx: &mut AppContext) { match &status { crate::Status::SigningIn { prompt } => { if let Some(code_verification_handle) = code_verification.as_mut() { - let window_id = code_verification_handle.id(); + let window_id = code_verification_handle.window_id(); let updated = cx.update_window(window_id, |cx| { code_verification_handle.update_root(cx, |code_verification, cx| { code_verification.set_status(status.clone(), cx) @@ -41,7 +41,7 @@ pub fn init(cx: &mut AppContext) { } Status::Authorized | Status::Unauthorized => { if let Some(code_verification) = code_verification.as_ref() { - let window_id = code_verification.id(); + let window_id = code_verification.window_id(); cx.update_window(window_id, |cx| { code_verification.update_root(cx, |code_verification, cx| { code_verification.set_status(status, cx) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 2444465be6..f2db0a7763 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -857,7 +857,7 @@ mod tests { let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); // Create some diagnostics project.update(cx, |project, cx| { @@ -1252,7 +1252,7 @@ mod tests { let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let view = cx.add_view(window_id, |cx| { ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a114cd437b..e8913505ca 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -525,7 +525,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) { let project = Project::test(fs, [], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); cx.add_view(window_id, |cx| { let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index f53115f224..d25ca7bb88 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -99,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> { Self { cx: EditorTestContext { cx, - window_id: window.id(), + window_id: window.window_id(), editor, }, lsp, diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index c7ea1b4f38..ac519764fd 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -39,7 +39,7 @@ impl<'a> EditorTestContext<'a> { let editor = window.root(cx); Self { cx, - window_id: window.id(), + window_id: window.window_id(), editor, } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 12bf324262..84a45b083e 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -619,7 +619,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - cx.dispatch_action(window.id(), Toggle); + cx.dispatch_action(window.window_id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); finder @@ -632,8 +632,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window.id(), SelectNext); - cx.dispatch_action(window.id(), Confirm); + cx.dispatch_action(window.window_id(), SelectNext); + cx.dispatch_action(window.window_id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -674,7 +674,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - cx.dispatch_action(window.id(), Toggle); + cx.dispatch_action(window.window_id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); let file_query = &first_file_name[..3]; @@ -706,8 +706,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window.id(), SelectNext); - cx.dispatch_action(window.id(), Confirm); + cx.dispatch_action(window.window_id(), SelectNext); + cx.dispatch_action(window.window_id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -758,7 +758,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - cx.dispatch_action(window.id(), Toggle); + cx.dispatch_action(window.window_id(), Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); let file_query = &first_file_name[..3]; @@ -790,8 +790,8 @@ mod tests { }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); - cx.dispatch_action(window.id(), SelectNext); - cx.dispatch_action(window.id(), Confirm); + cx.dispatch_action(window.window_id(), SelectNext); + cx.dispatch_action(window.window_id(), Confirm); active_pane .condition(cx, |pane, _| pane.active_item().is_some()) .await; @@ -1168,7 +1168,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -1376,7 +1376,7 @@ mod tests { let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1,); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index dce0b0e5f0..90f910d255 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1337,7 +1337,7 @@ impl AppContext { .open_window(window_id, window_options, this.foreground.clone()); let window = this.build_window(window_id, platform_window, build_root_view); this.windows.insert(window_id, window); - WindowHandle::new(window_id, this.ref_counts.clone()) + WindowHandle::new(window_id) }) } @@ -3863,21 +3863,21 @@ impl Clone for WeakModelHandle { impl Copy for WeakModelHandle {} pub struct WindowHandle { - any_handle: AnyWindowHandle, - view_type: PhantomData, + window_id: usize, + root_view_type: PhantomData, } #[allow(dead_code)] impl WindowHandle { - fn new(window_id: usize, ref_counts: Arc>) -> Self { + fn new(window_id: usize) -> Self { WindowHandle { - any_handle: AnyWindowHandle::new::(window_id, ref_counts), - view_type: PhantomData, + window_id, + root_view_type: PhantomData, } } - pub fn id(&self) -> usize { - self.any_handle.id() + pub fn window_id(&self) -> usize { + self.window_id } pub fn root(&self, cx: &C) -> C::Result> { @@ -3889,7 +3889,7 @@ impl WindowHandle { C: BorrowWindowContext, F: FnOnce(&WindowContext) -> R, { - cx.read_window_with(self.id(), |cx| read(cx)) + cx.read_window_with(self.window_id(), |cx| read(cx)) } pub fn update(&self, cx: &mut C, update: F) -> R @@ -3897,7 +3897,7 @@ impl WindowHandle { C: BorrowAppContext, F: FnOnce(&mut WindowContext) -> R, { - cx.update(|cx| cx.update_window(self.id(), update).unwrap()) + cx.update(|cx| cx.update_window(self.window_id(), update).unwrap()) } pub fn update_root(&self, cx: &mut C, update: F) -> R @@ -3905,7 +3905,7 @@ impl WindowHandle { C: BorrowAppContext, F: FnOnce(&mut V, &mut ViewContext) -> R, { - let window_id = self.id(); + let window_id = self.window_id(); cx.update(|cx| { cx.update_window(window_id, |cx| { cx.root_view() @@ -3920,7 +3920,9 @@ impl WindowHandle { pub fn read_root<'a>(&self, cx: &'a AppContext) -> &'a V { let root_view = cx - .read_window(self.id(), |cx| cx.root_view().clone().downcast().unwrap()) + .read_window(self.window_id(), |cx| { + cx.root_view().clone().downcast().unwrap() + }) .unwrap(); root_view.read(cx) } @@ -3948,66 +3950,6 @@ impl WindowHandle { } } -pub struct AnyWindowHandle { - window_id: usize, - root_view_type: TypeId, - ref_counts: Option>>, - - #[cfg(any(test, feature = "test-support"))] - handle_id: usize, -} - -impl AnyWindowHandle { - fn new(window_id: usize, ref_counts: Arc>) -> Self { - ref_counts.lock().inc_window(window_id); - - #[cfg(any(test, feature = "test-support"))] - let handle_id = ref_counts - .lock() - .leak_detector - .lock() - .handle_created(None, window_id); - - Self { - window_id, - root_view_type: TypeId::of::(), - ref_counts: Some(ref_counts), - #[cfg(any(test, feature = "test-support"))] - handle_id, - } - } - - pub fn id(&self) -> usize { - self.window_id - } - - pub fn downcast(self) -> Option> { - if TypeId::of::() == self.root_view_type { - Some(WindowHandle { - any_handle: self, - view_type: PhantomData, - }) - } else { - None - } - } -} - -impl Drop for AnyWindowHandle { - fn drop(&mut self) { - if let Some(ref_counts) = self.ref_counts.as_ref() { - ref_counts.lock().dec_window(self.window_id); - - #[cfg(any(test, feature = "test-support"))] - ref_counts - .lock() - .leak_detector - .lock() - .handle_dropped(self.window_id, self.handle_id); - } - } -} - #[repr(transparent)] pub struct ViewHandle { any_handle: AnyViewHandle, @@ -6281,7 +6223,7 @@ mod tests { // Check that global actions do not have a binding, even if a binding does exist in another view assert_eq!( - &available_actions(window.id(), view_1.id(), cx), + &available_actions(window.window_id(), view_1.id(), cx), &[ ("test::Action1", vec![Keystroke::parse("a").unwrap()]), ("test::GlobalAction", vec![]) @@ -6290,7 +6232,7 @@ mod tests { // Check that view 1 actions and bindings are available even when called from view 2 assert_eq!( - &available_actions(window.id(), view_2.id(), cx), + &available_actions(window.window_id(), view_2.id(), cx), &[ ("test::Action1", vec![Keystroke::parse("a").unwrap()]), ("test::Action2", vec![Keystroke::parse("b").unwrap()]), @@ -6353,7 +6295,7 @@ mod tests { ]); }); - let actions = cx.available_actions(window.id(), view.id()); + let actions = cx.available_actions(window.window_id(), view.id()); assert_eq!( actions[0].1.as_any().downcast_ref::(), Some(&ActionWithArg { arg: false }) @@ -6639,25 +6581,25 @@ mod tests { [("window 2", false), ("window 3", true)] ); - cx.simulate_window_activation(Some(window_2.id())); + cx.simulate_window_activation(Some(window_2.window_id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 3", false), ("window 2", true)] ); - cx.simulate_window_activation(Some(window_1.id())); + cx.simulate_window_activation(Some(window_1.window_id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 2", false), ("window 1", true)] ); - cx.simulate_window_activation(Some(window_3.id())); + cx.simulate_window_activation(Some(window_3.window_id())); assert_eq!( mem::take(&mut *events.borrow_mut()), [("window 1", false), ("window 3", true)] ); - cx.simulate_window_activation(Some(window_3.id())); + cx.simulate_window_activation(Some(window_3.window_id())); assert_eq!(mem::take(&mut *events.borrow_mut()), []); } diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs index 74563d05bc..f0c1699f16 100644 --- a/crates/gpui/src/app/ref_counts.rs +++ b/crates/gpui/src/app/ref_counts.rs @@ -25,7 +25,6 @@ struct ElementStateRefCount { pub struct RefCounts { entity_counts: HashMap, element_state_counts: HashMap, - dropped_windows: HashSet, dropped_models: HashSet, dropped_views: HashSet<(usize, usize)>, dropped_element_states: HashSet, @@ -44,18 +43,6 @@ impl RefCounts { } } - pub fn inc_window(&mut self, window_id: usize) { - match self.entity_counts.entry(window_id) { - Entry::Occupied(mut entry) => { - *entry.get_mut() += 1; - } - Entry::Vacant(entry) => { - entry.insert(1); - self.dropped_windows.remove(&window_id); - } - } - } - pub fn inc_model(&mut self, model_id: usize) { match self.entity_counts.entry(model_id) { Entry::Occupied(mut entry) => { @@ -98,15 +85,6 @@ impl RefCounts { } } - pub fn dec_window(&mut self, window_id: usize) { - let count = self.entity_counts.get_mut(&window_id).unwrap(); - *count -= 1; - if *count == 0 { - self.entity_counts.remove(&window_id); - self.dropped_windows.insert(window_id); - } - } - pub fn dec_model(&mut self, model_id: usize) { let count = self.entity_counts.get_mut(&model_id).unwrap(); *count -= 1; diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 0165c52e9f..3b574eb03e 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -157,9 +157,9 @@ impl TestAppContext { .cx .borrow_mut() .add_window(Default::default(), build_root_view); - self.simulate_window_activation(Some(window.id())); + self.simulate_window_activation(Some(window.window_id())); - WindowHandle::new(window.id(), self.cx.borrow_mut().ref_counts.clone()) + WindowHandle::new(window.window_id()) } pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 0149c310da..789341d46f 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1165,7 +1165,7 @@ impl<'a> WindowContext<'a> { let root_view = self.add_view(|cx| build_root_view(cx)); self.window.focused_view_id = Some(root_view.id()); self.window.root_view = Some(root_view.into_any()); - WindowHandle::new(self.window_id, self.ref_counts.clone()) + WindowHandle::new(self.window_id) } pub fn add_view(&mut self, build_view: F) -> ViewHandle diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 021ea2d3bc..80847f9f4b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1872,7 +1872,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "root1", cx); @@ -2225,7 +2225,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "root1", cx); @@ -2402,7 +2402,7 @@ mod tests { let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); toggle_expand_dir(&panel, "src/test", cx); @@ -2493,7 +2493,7 @@ mod tests { let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "src/", cx); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 8471f3a3a7..4bd186fc98 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -328,7 +328,7 @@ mod tests { let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); // Create the project symbols view. let symbols = cx.add_view(window_id, |cx| { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 1e635432bd..265e4f0206 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -851,11 +851,11 @@ mod tests { }); let window = cx.add_window(|_| EmptyView); - let editor = cx.add_view(window.id(), |cx| { + let editor = cx.add_view(window.window_id(), |cx| { Editor::for_buffer(buffer.clone(), None, cx) }); - let search_bar = cx.add_view(window.id(), |cx| { + let search_bar = cx.add_view(window.window_id(), |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); search_bar.show(cx); @@ -1232,7 +1232,7 @@ mod tests { ); let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); let window = cx.add_window(|_| EmptyView); - let window_id = window.id(); + let window_id = window.window_id(); let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); @@ -1421,11 +1421,11 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); let window = cx.add_window(|_| EmptyView); - let editor = cx.add_view(window.id(), |cx| { + let editor = cx.add_view(window.window_id(), |cx| { Editor::for_buffer(buffer.clone(), None, cx) }); - let search_bar = cx.add_view(window.id(), |cx| { + let search_bar = cx.add_view(window.window_id(), |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); search_bar.show(cx); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 0db66b4e37..febd564050 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1568,7 +1568,7 @@ pub mod tests { let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let active_item = cx.read(|cx| { workspace @@ -1874,7 +1874,7 @@ pub mod tests { let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); workspace.update(cx, |workspace, cx| { ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2efa9f8daa..09e4c4c219 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4196,14 +4196,14 @@ mod tests { ); }); assert_eq!( - cx.current_window_title(window.id()).as_deref(), + cx.current_window_title(window.window_id()).as_deref(), Some("one.txt — root1") ); // Add a second item to a non-empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); assert_eq!( - cx.current_window_title(window.id()).as_deref(), + cx.current_window_title(window.window_id()).as_deref(), Some("two.txt — root1") ); project.read_with(cx, |project, cx| { @@ -4222,7 +4222,7 @@ mod tests { .await .unwrap(); assert_eq!( - cx.current_window_title(window.id()).as_deref(), + cx.current_window_title(window.window_id()).as_deref(), Some("one.txt — root1") ); project.read_with(cx, |project, cx| { @@ -4242,14 +4242,14 @@ mod tests { .await .unwrap(); assert_eq!( - cx.current_window_title(window.id()).as_deref(), + cx.current_window_title(window.window_id()).as_deref(), Some("one.txt — root1, root2") ); // Remove a project folder project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); assert_eq!( - cx.current_window_title(window.id()).as_deref(), + cx.current_window_title(window.window_id()).as_deref(), Some("one.txt — root2") ); } @@ -4285,9 +4285,9 @@ mod tests { }); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); cx.foreground().run_until_parked(); - cx.simulate_prompt_answer(window.id(), 2 /* cancel */); + cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */); cx.foreground().run_until_parked(); - assert!(!cx.has_pending_prompt(window.id())); + assert!(!cx.has_pending_prompt(window.window_id())); assert!(!task.await.unwrap()); } @@ -4346,10 +4346,10 @@ mod tests { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); - assert!(cx.has_pending_prompt(window.id())); + assert!(cx.has_pending_prompt(window.window_id())); // Confirm saving item 1. - cx.simulate_prompt_answer(window.id(), 0); + cx.simulate_prompt_answer(window.window_id(), 0); cx.foreground().run_until_parked(); // Item 1 is saved. There's a prompt to save item 3. @@ -4360,10 +4360,10 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); - assert!(cx.has_pending_prompt(window.id())); + assert!(cx.has_pending_prompt(window.window_id())); // Cancel saving item 3. - cx.simulate_prompt_answer(window.id(), 1); + cx.simulate_prompt_answer(window.window_id(), 1); cx.foreground().run_until_parked(); // Item 3 is reloaded. There's a prompt to save item 4. @@ -4374,10 +4374,10 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); - assert!(cx.has_pending_prompt(window.id())); + assert!(cx.has_pending_prompt(window.window_id())); // Confirm saving item 4. - cx.simulate_prompt_answer(window.id(), 0); + cx.simulate_prompt_answer(window.window_id(), 0); cx.foreground().run_until_parked(); // There's a prompt for a path for item 4. @@ -4480,7 +4480,7 @@ mod tests { &[ProjectEntryId::from_proto(0)] ); }); - cx.simulate_prompt_answer(window.id(), 0); + cx.simulate_prompt_answer(window.window_id(), 0); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { @@ -4489,7 +4489,7 @@ mod tests { &[ProjectEntryId::from_proto(2)] ); }); - cx.simulate_prompt_answer(window.id(), 0); + cx.simulate_prompt_answer(window.window_id(), 0); cx.foreground().run_until_parked(); close.await.unwrap(); @@ -4549,7 +4549,7 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); // Deactivating the window still saves the file. - cx.simulate_window_activation(Some(window.id())); + cx.simulate_window_activation(Some(window.window_id())); item.update(cx, |item, cx| { cx.focus_self(); item.is_dirty = true; @@ -4591,7 +4591,7 @@ mod tests { pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) .await .unwrap(); - assert!(!cx.has_pending_prompt(window.id())); + assert!(!cx.has_pending_prompt(window.window_id())); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. @@ -4612,7 +4612,7 @@ mod tests { let _close_items = pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); deterministic.run_until_parked(); - assert!(cx.has_pending_prompt(window.id())); + assert!(cx.has_pending_prompt(window.window_id())); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a459122cfc..1c65317c41 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1299,7 +1299,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); // Open a file within an existing worktree. workspace @@ -1342,7 +1342,7 @@ mod tests { project.update(cx, |project, _| project.languages().add(rust_lang())); let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer @@ -1437,7 +1437,7 @@ mod tests { project.update(cx, |project, _| project.languages().add(rust_lang())); let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); // Create a new untitled buffer cx.dispatch_action(window_id, NewFile); @@ -1490,7 +1490,7 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = window.root(cx); - let window_id = window.id(); + let window_id = window.window_id(); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -2088,7 +2088,7 @@ mod tests { cx.foreground().run_until_parked(); let window = cx.add_window(|_| TestView); - let window_id = window.id(); + let window_id = window.window_id(); // Test loading the keymap base at all assert_key_bindings_for( @@ -2259,7 +2259,7 @@ mod tests { cx.foreground().run_until_parked(); let window = cx.add_window(|_| TestView); - let window_id = window.id(); + let window_id = window.window_id(); // Test loading the keymap base at all assert_key_bindings_for( From 2d96388be369259c5e8b6006bfe81cfe979889a3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 3 Aug 2023 17:46:34 -0600 Subject: [PATCH 9/9] Use WindowHandles in a couple places --- crates/copilot/src/sign_in.rs | 44 +++++++++++++++++------------------ crates/gpui/src/app.rs | 43 +++++++++++++++------------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 0d5bb28ed6..d03a2d393b 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -18,42 +18,42 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; pub fn init(cx: &mut AppContext) { if let Some(copilot) = Copilot::global(cx) { - let mut code_verification: Option> = None; + let mut verification_window: Option> = None; cx.observe(&copilot, move |copilot, cx| { let status = copilot.read(cx).status(); match &status { crate::Status::SigningIn { prompt } => { - if let Some(code_verification_handle) = code_verification.as_mut() { - let window_id = code_verification_handle.window_id(); - let updated = cx.update_window(window_id, |cx| { - code_verification_handle.update_root(cx, |code_verification, cx| { - code_verification.set_status(status.clone(), cx) - }); - cx.activate_window(); - }); - if updated.is_none() { - code_verification = Some(create_copilot_auth_window(cx, &status)); + if let Some(window) = verification_window.as_mut() { + let updated = window + .root(cx) + .map(|root| { + root.update(cx, |verification, cx| { + verification.set_status(status.clone(), cx); + cx.activate_window(); + }) + }) + .is_some(); + if !updated { + verification_window = Some(create_copilot_auth_window(cx, &status)); } } else if let Some(_prompt) = prompt { - code_verification = Some(create_copilot_auth_window(cx, &status)); + verification_window = Some(create_copilot_auth_window(cx, &status)); } } Status::Authorized | Status::Unauthorized => { - if let Some(code_verification) = code_verification.as_ref() { - let window_id = code_verification.window_id(); - cx.update_window(window_id, |cx| { - code_verification.update_root(cx, |code_verification, cx| { - code_verification.set_status(status, cx) + if let Some(window) = verification_window.as_ref() { + if let Some(verification) = window.root(cx) { + verification.update(cx, |verification, cx| { + verification.set_status(status, cx); + cx.platform().activate(true); + cx.activate_window(); }); - - cx.platform().activate(true); - cx.activate_window(); - }); + } } } _ => { - if let Some(code_verification) = code_verification.take() { + if let Some(code_verification) = verification_window.take() { code_verification.update(cx, |cx| cx.remove_window()); } } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 90f910d255..d98033820b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -808,7 +808,7 @@ impl AppContext { result } - pub fn read_window T>( + fn read_window T>( &self, window_id: usize, callback: F, @@ -3892,31 +3892,26 @@ impl WindowHandle { cx.read_window_with(self.window_id(), |cx| read(cx)) } - pub fn update(&self, cx: &mut C, update: F) -> R + pub fn update(&self, cx: &mut C, update: F) -> C::Result where - C: BorrowAppContext, + C: BorrowWindowContext, F: FnOnce(&mut WindowContext) -> R, { - cx.update(|cx| cx.update_window(self.window_id(), update).unwrap()) + cx.update_window(self.window_id(), update) } - pub fn update_root(&self, cx: &mut C, update: F) -> R - where - C: BorrowAppContext, - F: FnOnce(&mut V, &mut ViewContext) -> R, - { - let window_id = self.window_id(); - cx.update(|cx| { - cx.update_window(window_id, |cx| { - cx.root_view() - .clone() - .downcast::() - .unwrap() - .update(cx, update) - }) - .unwrap() - }) - } + // pub fn update_root(&self, cx: &mut C, update: F) -> C::Result> + // where + // C: BorrowWindowContext, + // F: FnOnce(&mut V, &mut ViewContext) -> R, + // { + // cx.update_window(self.window_id, |cx| { + // cx.root_view() + // .clone() + // .downcast::() + // .map(|v| v.update(cx, update)) + // }) + // } pub fn read_root<'a>(&self, cx: &'a AppContext) -> &'a V { let root_view = cx @@ -3940,9 +3935,9 @@ impl WindowHandle { }) } - pub fn add_view(&self, cx: &mut C, build_view: F) -> ViewHandle + pub fn add_view(&self, cx: &mut C, build_view: F) -> C::Result> where - C: BorrowAppContext, + C: BorrowWindowContext, U: View, F: FnOnce(&mut ViewContext) -> U, { @@ -4836,7 +4831,7 @@ mod tests { let called_defer = Rc::new(AtomicBool::new(false)); let called_after_window_update = Rc::new(AtomicBool::new(false)); - window.update_root(cx, |this, cx| { + window.root(cx).update(cx, |this, cx| { assert_eq!(this.render_count, 1); cx.defer({ let called_defer = called_defer.clone();