Merge pull request #12 from zed-industries/platform-trait

Nathan Sobo created

Hide platform code entirely behind App for framework users

Change summary

gpui/examples/text.rs                  |  12 
gpui/src/app.rs                        | 480 +++++++++++++++------------
gpui/src/lib.rs                        |   4 
gpui/src/platform/mac/app.rs           | 100 -----
gpui/src/platform/mac/mod.rs           |  16 
gpui/src/platform/mac/platform.rs      | 246 +++++++++----
gpui/src/platform/mod.rs               |  22 
gpui/src/platform/test.rs              |  34 +
gpui/src/presenter.rs                  |   4 
zed/src/editor/buffer/mod.rs           |  18 
zed/src/editor/buffer_element.rs       |  22 
zed/src/editor/buffer_view.rs          | 305 +++++++++--------
zed/src/editor/display_map/fold_map.rs | 307 ++++++++---------
zed/src/editor/display_map/mod.rs      |  99 ++--
zed/src/file_finder.rs                 |  59 +-
zed/src/main.rs                        |  79 ++--
zed/src/workspace/mod.rs               |  30 +
zed/src/workspace/pane.rs              |   4 
zed/src/workspace/workspace.rs         |  50 +-
zed/src/workspace/workspace_view.rs    | 103 +++--
zed/src/worktree/worktree.rs           |  42 +-
21 files changed, 1,036 insertions(+), 1,000 deletions(-)

Detailed changes

gpui/examples/text.rs 🔗

@@ -1,7 +1,6 @@
 use gpui::{
     color::ColorU,
     fonts::{Properties, Weight},
-    platform::{current as platform, Runner},
     DebugContext, Element as _, Quad,
 };
 use log::LevelFilter;
@@ -11,13 +10,10 @@ use simplelog::SimpleLogger;
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    let mut app = gpui::App::new(()).unwrap();
-    platform::runner()
-        .on_finish_launching(move || {
-            app.platform().activate(true);
-            app.add_window(|_| TextView);
-        })
-        .run();
+    gpui::App::new(()).unwrap().run(|ctx| {
+        ctx.platform().activate(true);
+        ctx.add_window(|_| TextView);
+    });
 }
 
 struct TextView;

gpui/src/app.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     elements::ElementBox,
     executor,
     keymap::{self, Keystroke},
-    platform::{self, App as _, WindowOptions},
+    platform::{self, WindowOptions},
     presenter::Presenter,
     util::post_inc,
     AssetCache, AssetSource, FontCache, TextLayoutCache,
@@ -21,6 +21,7 @@ use std::{
     fmt::{self, Debug},
     hash::{Hash, Hasher},
     marker::PhantomData,
+    path::PathBuf,
     rc::{self, Rc},
     sync::{Arc, Weak},
 };
@@ -44,8 +45,8 @@ pub trait View: Entity {
     }
 }
 
-pub trait ModelAsRef {
-    fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
+pub trait ReadModel {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
 }
 
 pub trait UpdateModel {
@@ -55,8 +56,8 @@ pub trait UpdateModel {
         F: FnOnce(&mut T, &mut ModelContext<T>) -> S;
 }
 
-pub trait ViewAsRef {
-    fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T;
+pub trait ReadView {
+    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T;
 }
 
 pub trait UpdateView {
@@ -83,24 +84,46 @@ pub enum MenuItem<'a> {
 #[derive(Clone)]
 pub struct App(Rc<RefCell<MutableAppContext>>);
 
+#[derive(Clone)]
+pub struct TestAppContext(Rc<RefCell<MutableAppContext>>);
+
 impl App {
-    pub fn test<T, A: AssetSource, F: Future<Output = T>, G: FnOnce(App) -> F>(
+    pub fn test<T, A: AssetSource, F: FnOnce(&mut MutableAppContext) -> T>(
         asset_source: A,
-        f: G,
+        f: F,
     ) -> T {
-        let platform = platform::test::app();
+        let platform = platform::test::platform();
         let foreground = Rc::new(executor::Foreground::test());
-        let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
+        let ctx = Rc::new(RefCell::new(MutableAppContext::new(
+            foreground,
+            Rc::new(platform),
+            asset_source,
+        )));
+        ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx));
+        let mut ctx = ctx.borrow_mut();
+        f(&mut *ctx)
+    }
+
+    pub fn test_async<T, F, A: AssetSource, Fn>(asset_source: A, f: Fn) -> T
+    where
+        Fn: FnOnce(TestAppContext) -> F,
+        F: Future<Output = T>,
+    {
+        let platform = platform::test::platform();
+        let foreground = Rc::new(executor::Foreground::test());
+        let ctx = TestAppContext(Rc::new(RefCell::new(MutableAppContext::new(
             foreground.clone(),
-            Arc::new(platform),
+            Rc::new(platform),
             asset_source,
         ))));
-        app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
-        smol::block_on(foreground.run(f(app)))
+        ctx.0.borrow_mut().weak_self = Some(Rc::downgrade(&ctx.0));
+
+        let future = f(ctx);
+        smol::block_on(foreground.run(future))
     }
 
     pub fn new(asset_source: impl AssetSource) -> Result<Self> {
-        let platform = Arc::new(platform::current::app());
+        let platform = platform::current::platform();
         let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
         let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
             foreground,
@@ -111,35 +134,98 @@ impl App {
         Ok(app)
     }
 
-    pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>(
-        &self,
-        window_id: usize,
-        callback: F,
-    ) {
+    pub fn on_become_active<F>(self, mut callback: F) -> Self
+    where
+        F: 'static + FnMut(&mut MutableAppContext),
+    {
+        let ctx = self.0.clone();
         self.0
-            .borrow_mut()
-            .on_window_invalidated(window_id, callback);
+            .borrow()
+            .platform
+            .on_become_active(Box::new(move || callback(&mut *ctx.borrow_mut())));
+        self
     }
 
-    pub fn add_action<S, V, T, F>(&self, name: S, handler: F)
+    pub fn on_resign_active<F>(self, mut callback: F) -> Self
     where
-        S: Into<String>,
-        V: View,
-        T: Any,
-        F: 'static + FnMut(&mut V, &T, &mut ViewContext<V>),
+        F: 'static + FnMut(&mut MutableAppContext),
     {
-        self.0.borrow_mut().add_action(name, handler);
+        let ctx = self.0.clone();
+        self.0
+            .borrow()
+            .platform
+            .on_resign_active(Box::new(move || callback(&mut *ctx.borrow_mut())));
+        self
     }
 
-    pub fn add_global_action<S, T, F>(&self, name: S, handler: F)
+    pub fn on_event<F>(self, mut callback: F) -> Self
     where
-        S: Into<String>,
-        T: 'static + Any,
-        F: 'static + FnMut(&T, &mut MutableAppContext),
+        F: 'static + FnMut(Event, &mut MutableAppContext) -> bool,
+    {
+        let ctx = self.0.clone();
+        self.0.borrow().platform.on_event(Box::new(move |event| {
+            callback(event, &mut *ctx.borrow_mut())
+        }));
+        self
+    }
+
+    pub fn on_menu_command<F>(self, mut callback: F) -> Self
+    where
+        F: 'static + FnMut(&str, &mut MutableAppContext),
+    {
+        let ctx = self.0.clone();
+        self.0
+            .borrow()
+            .platform
+            .on_menu_command(Box::new(move |command| {
+                callback(command, &mut *ctx.borrow_mut())
+            }));
+        self
+    }
+
+    pub fn on_open_files<F>(self, mut callback: F) -> Self
+    where
+        F: 'static + FnMut(Vec<PathBuf>, &mut MutableAppContext),
+    {
+        let ctx = self.0.clone();
+        self.0
+            .borrow()
+            .platform
+            .on_open_files(Box::new(move |paths| {
+                callback(paths, &mut *ctx.borrow_mut())
+            }));
+        self
+    }
+
+    pub fn set_menus(&self, menus: &[Menu]) {
+        self.0.borrow().platform.set_menus(menus);
+    }
+
+    pub fn run<F>(self, on_finish_launching: F)
+    where
+        F: 'static + FnOnce(&mut MutableAppContext),
     {
-        self.0.borrow_mut().add_global_action(name, handler);
+        let platform = self.0.borrow().platform.clone();
+        platform.run(Box::new(move || {
+            let mut ctx = self.0.borrow_mut();
+            on_finish_launching(&mut *ctx);
+        }))
     }
 
+    pub fn font_cache(&self) -> Arc<FontCache> {
+        self.0.borrow().font_cache.clone()
+    }
+
+    fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
+        let mut state = self.0.borrow_mut();
+        state.pending_flushes += 1;
+        let result = callback(&mut *state);
+        state.flush_effects();
+        result
+    }
+}
+
+impl TestAppContext {
     pub fn dispatch_action<T: 'static + Any>(
         &self,
         window_id: usize,
@@ -147,7 +233,7 @@ impl App {
         name: &str,
         arg: T,
     ) {
-        self.0.borrow_mut().dispatch_action(
+        self.0.borrow_mut().dispatch_action_any(
             window_id,
             &responder_chain,
             name,
@@ -155,16 +241,6 @@ impl App {
         );
     }
 
-    pub fn dispatch_global_action<T: 'static + Any>(&self, name: &str, arg: T) {
-        self.0
-            .borrow_mut()
-            .dispatch_global_action(name, Box::new(arg).as_ref());
-    }
-
-    pub fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&self, bindings: T) {
-        self.0.borrow_mut().add_bindings(bindings);
-    }
-
     pub fn dispatch_keystroke(
         &self,
         window_id: usize,
@@ -187,15 +263,6 @@ impl App {
         handle
     }
 
-    fn read_model<T, F, S>(&self, handle: &ModelHandle<T>, read: F) -> S
-    where
-        T: Entity,
-        F: FnOnce(&T, &AppContext) -> S,
-    {
-        let state = self.0.borrow();
-        read(state.model(handle), &state.ctx)
-    }
-
     pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
     where
         T: View,
@@ -240,27 +307,21 @@ impl App {
         handle
     }
 
-    pub fn read<T, F: FnOnce(&AppContext) -> T>(&mut self, callback: F) -> T {
-        callback(self.0.borrow().downgrade())
+    pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
+        callback(self.0.borrow().as_ref())
     }
 
     pub fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
         let mut state = self.0.borrow_mut();
-        state.pending_flushes += 1;
+        // Don't increment pending flushes in order to effects to be flushed before the callback
+        // completes, which is helpful in tests.
         let result = callback(&mut *state);
+        // Flush effects after the callback just in case there are any. This can happen in edge
+        // cases such as the closure dropping handles.
         state.flush_effects();
         result
     }
 
-    fn read_view<T, F, S>(&self, handle: &ViewHandle<T>, read: F) -> S
-    where
-        T: View,
-        F: FnOnce(&T, &AppContext) -> S,
-    {
-        let state = self.0.borrow();
-        read(state.view(handle), state.downgrade())
-    }
-
     pub fn finish_pending_tasks(&self) -> impl Future<Output = ()> {
         self.0.borrow().finish_pending_tasks()
     }
@@ -269,12 +330,12 @@ impl App {
         self.0.borrow().font_cache.clone()
     }
 
-    pub fn platform(&self) -> Arc<dyn platform::App> {
+    pub fn platform(&self) -> Rc<dyn platform::Platform> {
         self.0.borrow().platform.clone()
     }
 }
 
-impl UpdateModel for App {
+impl UpdateModel for TestAppContext {
     fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
     where
         T: Entity,
@@ -288,7 +349,7 @@ impl UpdateModel for App {
     }
 }
 
-impl UpdateView for App {
+impl UpdateView for TestAppContext {
     fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
     where
         T: View,
@@ -309,7 +370,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext);
 
 pub struct MutableAppContext {
     weak_self: Option<rc::Weak<RefCell<Self>>>,
-    platform: Arc<dyn platform::App>,
+    platform: Rc<dyn platform::Platform>,
     font_cache: Arc<FontCache>,
     assets: Arc<AssetCache>,
     ctx: AppContext,
@@ -337,7 +398,7 @@ pub struct MutableAppContext {
 impl MutableAppContext {
     pub fn new(
         foreground: Rc<executor::Foreground>,
-        platform: Arc<dyn platform::App>,
+        platform: Rc<dyn platform::Platform>,
         asset_source: impl AssetSource,
     ) -> Self {
         let fonts = platform.fonts();
@@ -377,12 +438,12 @@ impl MutableAppContext {
         App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
     }
 
-    pub fn downgrade(&self) -> &AppContext {
-        &self.ctx
+    pub fn platform(&self) -> Rc<dyn platform::Platform> {
+        self.platform.clone()
     }
 
-    pub fn platform(&self) -> Arc<dyn platform::App> {
-        self.platform.clone()
+    pub fn font_cache(&self) -> &Arc<FontCache> {
+        &self.font_cache
     }
 
     pub fn foreground_executor(&self) -> &Rc<executor::Foreground> {
@@ -505,7 +566,24 @@ impl MutableAppContext {
         self.ctx.render_views(window_id)
     }
 
-    pub fn dispatch_action(
+    pub fn update<T, F: FnOnce() -> T>(&mut self, callback: F) -> T {
+        self.pending_flushes += 1;
+        let result = callback();
+        self.flush_effects();
+        result
+    }
+
+    pub fn dispatch_action<T: 'static + Any>(
+        &mut self,
+        window_id: usize,
+        responder_chain: Vec<usize>,
+        name: &str,
+        arg: T,
+    ) {
+        self.dispatch_action_any(window_id, &responder_chain, name, Box::new(arg).as_ref());
+    }
+
+    fn dispatch_action_any(
         &mut self,
         window_id: usize,
         path: &[usize],
@@ -556,14 +634,18 @@ impl MutableAppContext {
         }
 
         if !halted_dispatch {
-            self.dispatch_global_action(name, arg);
+            self.dispatch_global_action_with_dyn_arg(name, arg);
         }
 
         self.flush_effects();
         halted_dispatch
     }
 
-    fn dispatch_global_action(&mut self, name: &str, arg: &dyn Any) {
+    pub fn dispatch_global_action<T: 'static + Any>(&mut self, name: &str, arg: T) {
+        self.dispatch_global_action_with_dyn_arg(name, Box::new(arg).as_ref());
+    }
+
+    fn dispatch_global_action_with_dyn_arg(&mut self, name: &str, arg: &dyn Any) {
         if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) {
             self.pending_flushes += 1;
             for handler in handlers.iter_mut().rev() {
@@ -574,7 +656,7 @@ impl MutableAppContext {
         }
     }
 
-    fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&mut self, bindings: T) {
+    pub fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&mut self, bindings: T) {
         self.keystroke_matcher.add_bindings(bindings);
     }
 
@@ -593,7 +675,7 @@ impl MutableAppContext {
                 .get(&window_id)
                 .and_then(|w| w.views.get(view_id))
             {
-                context.extend(view.keymap_context(self.downgrade()));
+                context.extend(view.keymap_context(self.as_ref()));
                 context_chain.push(context.clone());
             } else {
                 return Err(anyhow!(
@@ -612,7 +694,7 @@ impl MutableAppContext {
                 MatchResult::None => {}
                 MatchResult::Pending => pending = true,
                 MatchResult::Action { name, arg } => {
-                    if self.dispatch_action(
+                    if self.dispatch_action_any(
                         window_id,
                         &responder_chain[0..=i],
                         &name,
@@ -686,7 +768,7 @@ impl MutableAppContext {
                                 if ctx
                                     .dispatch_keystroke(
                                         window_id,
-                                        presenter.borrow().dispatch_path(ctx.downgrade()),
+                                        presenter.borrow().dispatch_path(ctx.as_ref()),
                                         keystroke,
                                     )
                                     .unwrap()
@@ -695,11 +777,10 @@ impl MutableAppContext {
                                 }
                             }
 
-                            let actions = presenter
-                                .borrow_mut()
-                                .dispatch_event(event, ctx.downgrade());
+                            let actions =
+                                presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
                             for action in actions {
-                                ctx.dispatch_action(
+                                ctx.dispatch_action_any(
                                     window_id,
                                     &action.path,
                                     action.name,
@@ -729,7 +810,7 @@ impl MutableAppContext {
                     let presenter = presenter.clone();
                     self.on_window_invalidated(window_id, move |invalidation, ctx| {
                         let mut presenter = presenter.borrow_mut();
-                        presenter.invalidate(invalidation, ctx.downgrade());
+                        presenter.invalidate(invalidation, ctx.as_ref());
                         let scene =
                             presenter.build_scene(window.size(), window.scale_factor(), ctx);
                         window.present_scene(scene);
@@ -812,7 +893,7 @@ impl MutableAppContext {
     }
 
     fn flush_effects(&mut self) {
-        self.pending_flushes -= 1;
+        self.pending_flushes = self.pending_flushes.saturating_sub(1);
 
         if !self.flushing_effects && self.pending_flushes == 0 {
             self.flushing_effects = true;
@@ -1144,8 +1225,8 @@ impl MutableAppContext {
     }
 }
 
-impl ModelAsRef for MutableAppContext {
-    fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+impl ReadModel for MutableAppContext {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
         if let Some(model) = self.ctx.models.get(&handle.model_id) {
             model
                 .as_any()
@@ -1182,8 +1263,8 @@ impl UpdateModel for MutableAppContext {
     }
 }
 
-impl ViewAsRef for MutableAppContext {
-    fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+impl ReadView for MutableAppContext {
+    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
         if let Some(window) = self.ctx.windows.get(&handle.window_id) {
             if let Some(view) = window.views.get(&handle.view_id) {
                 view.as_any().downcast_ref().expect("Downcast is type safe")
@@ -1231,6 +1312,12 @@ impl UpdateView for MutableAppContext {
     }
 }
 
+impl AsRef<AppContext> for MutableAppContext {
+    fn as_ref(&self) -> &AppContext {
+        &self.ctx
+    }
+}
+
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     windows: HashMap<usize, Window>,
@@ -1276,8 +1363,8 @@ impl AppContext {
     }
 }
 
-impl ModelAsRef for AppContext {
-    fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+impl ReadModel for AppContext {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
         if let Some(model) = self.models.get(&handle.model_id) {
             model
                 .as_any()
@@ -1289,8 +1376,8 @@ impl ModelAsRef for AppContext {
     }
 }
 
-impl ViewAsRef for AppContext {
-    fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+impl ReadView for AppContext {
+    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
         if let Some(window) = self.windows.get(&handle.window_id) {
             if let Some(view) = window.views.get(&handle.view_id) {
                 view.as_any()
@@ -1561,9 +1648,9 @@ impl<'a, T: Entity> ModelContext<'a, T> {
     }
 }
 
-impl<M> ModelAsRef for ModelContext<'_, M> {
-    fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
-        self.app.model(handle)
+impl<M> ReadModel for ModelContext<'_, M> {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+        self.app.read_model(handle)
     }
 }
 
@@ -1679,7 +1766,7 @@ impl<'a, T: View> ViewContext<'a, T> {
                 window_id: self.window_id,
                 view_id: self.view_id,
                 callback: Box::new(move |view, payload, app, window_id, view_id| {
-                    if let Some(emitter_handle) = emitter_handle.upgrade(app.downgrade()) {
+                    if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) {
                         let model = view.downcast_mut().expect("downcast is type safe");
                         let payload = payload.downcast_ref().expect("downcast is type safe");
                         let mut ctx = ViewContext::new(app, window_id, view_id);
@@ -1705,7 +1792,7 @@ impl<'a, T: View> ViewContext<'a, T> {
                 window_id: self.window_id,
                 view_id: self.view_id,
                 callback: Box::new(move |view, payload, app, window_id, view_id| {
-                    if let Some(emitter_handle) = emitter_handle.upgrade(app.downgrade()) {
+                    if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) {
                         let model = view.downcast_mut().expect("downcast is type safe");
                         let payload = payload.downcast_ref().expect("downcast is type safe");
                         let mut ctx = ViewContext::new(app, window_id, view_id);
@@ -1816,9 +1903,9 @@ impl<'a, T: View> ViewContext<'a, T> {
     }
 }
 
-impl<V> ModelAsRef for ViewContext<'_, V> {
-    fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
-        self.app.model(handle)
+impl<V> ReadModel for ViewContext<'_, V> {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+        self.app.read_model(handle)
     }
 }
 
@@ -1832,9 +1919,9 @@ impl<V: View> UpdateModel for ViewContext<'_, V> {
     }
 }
 
-impl<V: View> ViewAsRef for ViewContext<'_, V> {
-    fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
-        self.app.view(handle)
+impl<V: View> ReadView for ViewContext<'_, V> {
+    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+        self.app.read_view(handle)
     }
 }
 
@@ -1883,15 +1970,8 @@ impl<T: Entity> ModelHandle<T> {
         self.model_id
     }
 
-    pub fn as_ref<'a, A: ModelAsRef>(&self, app: &'a A) -> &'a T {
-        app.model(self)
-    }
-
-    pub fn read<'a, S, F>(&self, app: &'a App, read: F) -> S
-    where
-        F: FnOnce(&T, &AppContext) -> S,
-    {
-        app.read_model(self, read)
+    pub fn read<'a, A: ReadModel>(&self, app: &'a A) -> &'a T {
+        app.read_model(self)
     }
 
     pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
@@ -2018,15 +2098,8 @@ impl<T: View> ViewHandle<T> {
         self.view_id
     }
 
-    pub fn as_ref<'a, A: ViewAsRef>(&self, app: &'a A) -> &'a T {
-        app.view(self)
-    }
-
-    pub fn read<'a, F, S>(&self, app: &'a App, read: F) -> S
-    where
-        F: FnOnce(&T, &AppContext) -> S,
-    {
-        app.read_view(self, read)
+    pub fn read<'a, A: ReadView>(&self, app: &'a A) -> &'a T {
+        app.read_view(self)
     }
 
     pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
@@ -2362,12 +2435,10 @@ mod tests {
             }
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
-
+        App::test((), |app| {
             let handle_1 = app.add_model(|ctx| Model::new(None, ctx));
             let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), ctx));
-            assert_eq!(app.0.borrow().ctx.models.len(), 2);
+            assert_eq!(app.ctx.models.len(), 2);
 
             handle_1.update(app, |model, ctx| {
                 model.events.push("updated".into());
@@ -2375,30 +2446,25 @@ mod tests {
                 ctx.notify();
                 ctx.emit(2);
             });
-            handle_1.read(app, |model, _| {
-                assert_eq!(model.events, vec!["updated".to_string()]);
-            });
-            handle_2.read(app, |model, _| {
-                assert_eq!(
-                    model.events,
-                    vec![
-                        "observed event 1".to_string(),
-                        "notified".to_string(),
-                        "observed event 2".to_string(),
-                    ]
-                );
-            });
+            assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+            assert_eq!(
+                handle_2.read(app).events,
+                vec![
+                    "observed event 1".to_string(),
+                    "notified".to_string(),
+                    "observed event 2".to_string(),
+                ]
+            );
 
             handle_2.update(app, |model, _| {
                 drop(handle_1);
                 model.other.take();
             });
 
-            let app_state = app.0.borrow();
-            assert_eq!(app_state.ctx.models.len(), 1);
-            assert!(app_state.subscriptions.is_empty());
-            assert!(app_state.observations.is_empty());
-        })
+            assert_eq!(app.ctx.models.len(), 1);
+            assert!(app.subscriptions.is_empty());
+            assert!(app.observations.is_empty());
+        });
     }
 
     #[test]
@@ -2412,8 +2478,7 @@ mod tests {
             type Event = usize;
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let handle_1 = app.add_model(|_| Model::default());
             let handle_2 = app.add_model(|_| Model::default());
             let handle_2b = handle_2.clone();
@@ -2429,10 +2494,10 @@ mod tests {
             });
 
             handle_2.update(app, |_, c| c.emit(7));
-            handle_1.read(app, |model, _| assert_eq!(model.events, vec![7]));
+            assert_eq!(handle_1.read(app).events, vec![7]);
 
             handle_2.update(app, |_, c| c.emit(5));
-            handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5]));
+            assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
         })
     }
 
@@ -2448,17 +2513,16 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let handle_1 = app.add_model(|_| Model::default());
             let handle_2 = app.add_model(|_| Model::default());
             let handle_2b = handle_2.clone();
 
             handle_1.update(app, |_, c| {
                 c.observe(&handle_2, move |model, observed, c| {
-                    model.events.push(observed.as_ref(c).count);
+                    model.events.push(observed.read(c).count);
                     c.observe(&handle_2b, |model, observed, c| {
-                        model.events.push(observed.as_ref(c).count * 2);
+                        model.events.push(observed.read(c).count * 2);
                     });
                 });
             });
@@ -2467,13 +2531,13 @@ mod tests {
                 model.count = 7;
                 c.notify()
             });
-            handle_1.read(app, |model, _| assert_eq!(model.events, vec![7]));
+            assert_eq!(handle_1.read(app).events, vec![7]);
 
             handle_2.update(app, |model, c| {
                 model.count = 5;
                 c.notify()
             });
-            handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5]))
+            assert_eq!(handle_1.read(app).events, vec![7, 10, 5])
         })
     }
 
@@ -2488,7 +2552,7 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
+        App::test_async((), |mut app| async move {
             let handle = app.add_model(|_| Model::default());
             handle
                 .update(&mut app, |_, c| {
@@ -2497,7 +2561,7 @@ mod tests {
                     })
                 })
                 .await;
-            handle.read(&app, |model, _| assert_eq!(model.count, 7));
+            app.read(|ctx| assert_eq!(handle.read(ctx).count, 7));
 
             handle
                 .update(&mut app, |_, c| {
@@ -2506,7 +2570,7 @@ mod tests {
                     })
                 })
                 .await;
-            handle.read(&app, |model, _| assert_eq!(model.count, 14));
+            app.read(|ctx| assert_eq!(handle.read(ctx).count, 14));
         });
     }
 
@@ -2521,7 +2585,7 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
+        App::test_async((), |mut app| async move {
             let handle = app.add_model(|_| Model::default());
             handle
                 .update(&mut app, |_, c| {
@@ -2536,10 +2600,7 @@ mod tests {
                     )
                 })
                 .await;
-
-            handle.read(&app, |model, _| {
-                assert_eq!(model.events, [Some(1), Some(2), Some(3), None])
-            });
+            app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None]));
         })
     }
 
@@ -2578,40 +2639,34 @@ mod tests {
             }
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let (window_id, _) = app.add_window(|ctx| View::new(None, ctx));
             let handle_1 = app.add_view(window_id, |ctx| View::new(None, ctx));
             let handle_2 = app.add_view(window_id, |ctx| View::new(Some(handle_1.clone()), ctx));
-            assert_eq!(app.0.borrow().ctx.windows[&window_id].views.len(), 3);
+            assert_eq!(app.ctx.windows[&window_id].views.len(), 3);
 
             handle_1.update(app, |view, ctx| {
                 view.events.push("updated".into());
                 ctx.emit(1);
                 ctx.emit(2);
             });
-            handle_1.read(app, |view, _| {
-                assert_eq!(view.events, vec!["updated".to_string()]);
-            });
-            handle_2.read(app, |view, _| {
-                assert_eq!(
-                    view.events,
-                    vec![
-                        "observed event 1".to_string(),
-                        "observed event 2".to_string(),
-                    ]
-                );
-            });
+            assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+            assert_eq!(
+                handle_2.read(app).events,
+                vec![
+                    "observed event 1".to_string(),
+                    "observed event 2".to_string(),
+                ]
+            );
 
             handle_2.update(app, |view, _| {
                 drop(handle_1);
                 view.other.take();
             });
 
-            let app_state = app.0.borrow();
-            assert_eq!(app_state.ctx.windows[&window_id].views.len(), 2);
-            assert!(app_state.subscriptions.is_empty());
-            assert!(app_state.observations.is_empty());
+            assert_eq!(app.ctx.windows[&window_id].views.len(), 2);
+            assert!(app.subscriptions.is_empty());
+            assert!(app.observations.is_empty());
         })
     }
 
@@ -2642,8 +2697,7 @@ mod tests {
             type Event = usize;
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let (window_id, handle_1) = app.add_window(|_| View::default());
             let handle_2 = app.add_view(window_id, |_| View::default());
             let handle_2b = handle_2.clone();
@@ -2664,13 +2718,13 @@ mod tests {
             });
 
             handle_2.update(app, |_, c| c.emit(7));
-            handle_1.read(app, |view, _| assert_eq!(view.events, vec![7]));
+            assert_eq!(handle_1.read(app).events, vec![7]);
 
             handle_2.update(app, |_, c| c.emit(5));
-            handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5]));
+            assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
 
             handle_3.update(app, |_, c| c.emit(9));
-            handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5, 9]));
+            assert_eq!(handle_1.read(app).events, vec![7, 10, 5, 9]);
         })
     }
 
@@ -2698,9 +2752,7 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
-
+        App::test((), |app| {
             let (window_id, _) = app.add_window(|_| View);
             let observing_view = app.add_view(window_id, |_| View);
             let emitting_view = app.add_view(window_id, |_| View);
@@ -2715,7 +2767,7 @@ mod tests {
                 ctx.subscribe(&observed_model, |_, _, _| {});
             });
 
-            app.update(|_| {
+            app.update(|| {
                 drop(observing_view);
                 drop(observing_model);
             });
@@ -2755,14 +2807,13 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let (_, view) = app.add_window(|_| View::default());
             let model = app.add_model(|_| Model::default());
 
             view.update(app, |_, c| {
                 c.observe(&model, |me, observed, c| {
-                    me.events.push(observed.as_ref(c).count)
+                    me.events.push(observed.read(c).count)
                 });
             });
 
@@ -2770,7 +2821,7 @@ mod tests {
                 model.count = 11;
                 c.notify();
             });
-            view.read(app, |view, _| assert_eq!(view.events, vec![11]));
+            assert_eq!(view.read(app).events, vec![11]);
         })
     }
 
@@ -2798,9 +2849,7 @@ mod tests {
             type Event = ();
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
-
+        App::test((), |app| {
             let (window_id, _) = app.add_window(|_| View);
             let observing_view = app.add_view(window_id, |_| View);
             let observing_model = app.add_model(|_| Model);
@@ -2813,7 +2862,7 @@ mod tests {
                 ctx.observe(&observed_model, |_, _, _| {});
             });
 
-            app.update(|_| {
+            app.update(|| {
                 drop(observing_view);
                 drop(observing_model);
             });
@@ -2853,8 +2902,7 @@ mod tests {
             }
         }
 
-        App::test((), |mut app| async move {
-            let app = &mut app;
+        App::test((), |app| {
             let (window_id, view_1) = app.add_window(|_| View::default());
             let view_2 = app.add_view(window_id, |_| View::default());
 
@@ -2869,18 +2917,16 @@ mod tests {
                 ctx.focus(&view_1);
             });
 
-            view_1.read(app, |view_1, _| {
-                assert_eq!(
-                    view_1.events,
-                    [
-                        "self focused".to_string(),
-                        "self blurred".to_string(),
-                        "view 2 focused".to_string(),
-                        "self focused".to_string(),
-                        "view 2 blurred".to_string(),
-                    ],
-                );
-            });
+            assert_eq!(
+                view_1.read(app).events,
+                [
+                    "self focused".to_string(),
+                    "self blurred".to_string(),
+                    "view 2 focused".to_string(),
+                    "self focused".to_string(),
+                    "view 2 blurred".to_string(),
+                ],
+            );
         })
     }
 
@@ -2905,8 +2951,8 @@ mod tests {
             }
         }
 
-        App::test((), |mut app| async move {
-            let (_, handle) = app.add_window(|_| View::default());
+        App::test_async((), |mut app| async move {
+            let handle = app.add_window(|_| View::default()).1;
             handle
                 .update(&mut app, |_, c| {
                     c.spawn(async { 7 }, |me, output, _| {
@@ -2914,7 +2960,7 @@ mod tests {
                     })
                 })
                 .await;
-            handle.read(&app, |view, _| assert_eq!(view.count, 7));
+            app.read(|ctx| assert_eq!(handle.read(ctx).count, 7));
             handle
                 .update(&mut app, |_, c| {
                     c.spawn(async { 14 }, |me, output, _| {
@@ -2922,7 +2968,7 @@ mod tests {
                     })
                 })
                 .await;
-            handle.read(&app, |view, _| assert_eq!(view.count, 14));
+            app.read(|ctx| assert_eq!(handle.read(ctx).count, 14));
         });
     }
 
@@ -2947,7 +2993,7 @@ mod tests {
             }
         }
 
-        App::test((), |mut app| async move {
+        App::test_async((), |mut app| async move {
             let (_, handle) = app.add_window(|_| View::default());
             handle
                 .update(&mut app, |_, c| {

gpui/src/lib.rs 🔗

@@ -21,8 +21,8 @@ pub use executor::Task;
 pub mod color;
 pub mod json;
 pub mod keymap;
-pub mod platform;
-pub use platform::Event;
+mod platform;
+pub use platform::{Event, PathPromptOptions};
 pub use presenter::{
     AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
     SizeConstraint, Vector2FExt,

gpui/src/platform/mac/app.rs 🔗

@@ -1,100 +0,0 @@
-use super::{BoolExt as _, Dispatcher, FontSystem, Window};
-use crate::{executor, platform};
-use anyhow::Result;
-use cocoa::{
-    appkit::{NSApplication, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString},
-    base::nil,
-    foundation::{NSArray, NSData, NSString, NSURL},
-};
-use objc::{msg_send, sel, sel_impl};
-use std::{ffi::c_void, path::PathBuf, rc::Rc, sync::Arc};
-
-pub struct App {
-    dispatcher: Arc<Dispatcher>,
-    fonts: Arc<FontSystem>,
-}
-
-impl App {
-    pub fn new() -> Self {
-        Self {
-            dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(FontSystem::new()),
-        }
-    }
-}
-
-impl platform::App for App {
-    fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
-        self.dispatcher.clone()
-    }
-
-    fn activate(&self, ignoring_other_apps: bool) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
-        }
-    }
-
-    fn open_window(
-        &self,
-        options: platform::WindowOptions,
-        executor: Rc<executor::Foreground>,
-    ) -> Result<Box<dyn platform::Window>> {
-        Ok(Box::new(Window::open(options, executor, self.fonts())?))
-    }
-
-    fn prompt_for_paths(
-        &self,
-        options: platform::PathPromptOptions,
-    ) -> Option<Vec<std::path::PathBuf>> {
-        unsafe {
-            let panel = NSOpenPanel::openPanel(nil);
-            panel.setCanChooseDirectories_(options.directories.to_objc());
-            panel.setCanChooseFiles_(options.files.to_objc());
-            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
-            panel.setResolvesAliases_(false.to_objc());
-            let response = panel.runModal();
-            if response == NSModalResponse::NSModalResponseOk {
-                let mut result = Vec::new();
-                let urls = panel.URLs();
-                for i in 0..urls.count() {
-                    let url = urls.objectAtIndex(i);
-                    let string = url.absoluteString();
-                    let string = std::ffi::CStr::from_ptr(string.UTF8String())
-                        .to_string_lossy()
-                        .to_string();
-                    if let Some(path) = string.strip_prefix("file://") {
-                        result.push(PathBuf::from(path));
-                    }
-                }
-                Some(result)
-            } else {
-                None
-            }
-        }
-    }
-
-    fn fonts(&self) -> Arc<dyn platform::FontSystem> {
-        self.fonts.clone()
-    }
-
-    fn quit(&self) {
-        unsafe {
-            let app = NSApplication::sharedApplication(nil);
-            let _: () = msg_send![app, terminate: nil];
-        }
-    }
-
-    fn copy(&self, text: &str) {
-        unsafe {
-            let data = NSData::dataWithBytes_length_(
-                nil,
-                text.as_ptr() as *const c_void,
-                text.len() as u64,
-            );
-            let pasteboard = NSPasteboard::generalPasteboard(nil);
-            pasteboard.clearContents();
-            pasteboard.setData_forType(data, NSPasteboardTypeString);
-        }
-    }
-}

gpui/src/platform/mac/mod.rs 🔗

@@ -1,28 +1,22 @@
-mod app;
 mod atlas;
 mod dispatcher;
 mod event;
 mod fonts;
 mod geometry;
+mod platform;
 mod renderer;
-mod runner;
 mod sprite_cache;
 mod window;
 
-use crate::platform;
-pub use app::App;
 use cocoa::base::{BOOL, NO, YES};
 pub use dispatcher::Dispatcher;
 pub use fonts::FontSystem;
-pub use runner::Runner;
+use platform::MacPlatform;
+use std::rc::Rc;
 use window::Window;
 
-pub fn app() -> impl platform::App {
-    App::new()
-}
-
-pub fn runner() -> impl platform::Runner {
-    Runner::new()
+pub fn platform() -> Rc<dyn super::Platform> {
+    Rc::new(MacPlatform::new())
 }
 
 trait BoolExt {

gpui/src/platform/mac/runner.rs → gpui/src/platform/mac/platform.rs 🔗

@@ -1,11 +1,14 @@
-use crate::{keymap::Keystroke, platform::Event, Menu, MenuItem};
+use super::{BoolExt as _, Dispatcher, FontSystem, Window};
+use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
+use anyhow::Result;
 use cocoa::{
     appkit::{
         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
-        NSEventModifierFlags, NSMenu, NSMenuItem, NSWindow,
+        NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
+        NSPasteboardTypeString, NSWindow,
     },
     base::{id, nil, selector},
-    foundation::{NSArray, NSAutoreleasePool, NSInteger, NSString},
+    foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL},
 };
 use ctor::ctor;
 use objc::{
@@ -15,14 +18,18 @@ use objc::{
     runtime::{Class, Object, Sel},
     sel, sel_impl,
 };
+use ptr::null_mut;
 use std::{
-    ffi::CStr,
-    os::raw::{c_char, c_void},
+    cell::RefCell,
+    ffi::{c_void, CStr},
+    os::raw::c_char,
     path::PathBuf,
     ptr,
+    rc::Rc,
+    sync::Arc,
 };
 
-const RUNNER_IVAR: &'static str = "runner";
+const MAC_PLATFORM_IVAR: &'static str = "platform";
 static mut APP_CLASS: *const Class = ptr::null();
 static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
 
@@ -30,7 +37,7 @@ static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
 unsafe fn build_classes() {
     APP_CLASS = {
         let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
-        decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
         decl.add_method(
             sel!(sendEvent:),
             send_event as extern "C" fn(&mut Object, Sel, id),
@@ -40,7 +47,7 @@ unsafe fn build_classes() {
 
     APP_DELEGATE_CLASS = {
         let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
-        decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
         decl.add_method(
             sel!(applicationDidFinishLaunching:),
             did_finish_launching as extern "C" fn(&mut Object, Sel, id),
@@ -65,25 +72,37 @@ unsafe fn build_classes() {
     }
 }
 
+pub struct MacPlatform {
+    dispatcher: Arc<Dispatcher>,
+    fonts: Arc<FontSystem>,
+    callbacks: RefCell<Callbacks>,
+    menu_item_actions: RefCell<Vec<String>>,
+}
+
 #[derive(Default)]
-pub struct Runner {
-    finish_launching_callback: Option<Box<dyn FnOnce()>>,
-    become_active_callback: Option<Box<dyn FnMut()>>,
-    resign_active_callback: Option<Box<dyn FnMut()>>,
-    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
-    open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
-    menu_command_callback: Option<Box<dyn FnMut(&str)>>,
-    menu_item_actions: Vec<String>,
+struct Callbacks {
+    become_active: Option<Box<dyn FnMut()>>,
+    resign_active: Option<Box<dyn FnMut()>>,
+    event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
+    menu_command: Option<Box<dyn FnMut(&str)>>,
+    open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+    finish_launching: Option<Box<dyn FnOnce() -> ()>>,
 }
 
-impl Runner {
+impl MacPlatform {
     pub fn new() -> Self {
-        Default::default()
+        Self {
+            dispatcher: Arc::new(Dispatcher),
+            fonts: Arc::new(FontSystem::new()),
+            callbacks: Default::default(),
+            menu_item_actions: Default::default(),
+        }
     }
 
-    unsafe fn create_menu_bar(&mut self, menus: &[Menu]) -> id {
+    unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id {
         let menu_bar = NSMenu::new(nil).autorelease();
-        self.menu_item_actions.clear();
+        let mut menu_item_actions = self.menu_item_actions.borrow_mut();
+        menu_item_actions.clear();
 
         for menu_config in menus {
             let menu_bar_item = NSMenuItem::new(nil).autorelease();
@@ -140,9 +159,9 @@ impl Runner {
                                 .autorelease();
                         }
 
-                        let tag = self.menu_item_actions.len() as NSInteger;
+                        let tag = menu_item_actions.len() as NSInteger;
                         let _: () = msg_send![item, setTag: tag];
-                        self.menu_item_actions.push(action.to_string());
+                        menu_item_actions.push(action.to_string());
                     }
                 }
 
@@ -157,85 +176,148 @@ impl Runner {
     }
 }
 
-impl crate::platform::Runner for Runner {
-    fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
-        self.finish_launching_callback = Some(Box::new(callback));
-        self
+impl platform::Platform for MacPlatform {
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+        self.callbacks.borrow_mut().become_active = Some(callback);
     }
 
-    fn on_menu_command<F: 'static + FnMut(&str)>(mut self, callback: F) -> Self {
-        self.menu_command_callback = Some(Box::new(callback));
-        self
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+        self.callbacks.borrow_mut().resign_active = Some(callback);
     }
 
-    fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
-        log::info!("become active");
-        self.become_active_callback = Some(Box::new(callback));
-        self
+    fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
+        self.callbacks.borrow_mut().event = Some(callback);
     }
 
-    fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
-        self.resign_active_callback = Some(Box::new(callback));
-        self
+    fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>) {
+        self.callbacks.borrow_mut().menu_command = Some(callback);
     }
 
-    fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
-        self.event_callback = Some(Box::new(callback));
-        self
+    fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>) {
+        self.callbacks.borrow_mut().open_files = Some(callback);
     }
 
-    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
-        self.open_files_callback = Some(Box::new(callback));
-        self
-    }
+    fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>) {
+        self.callbacks.borrow_mut().finish_launching = Some(on_finish_launching);
 
-    fn set_menus(mut self, menus: &[Menu]) -> Self {
         unsafe {
             let app: id = msg_send![APP_CLASS, sharedApplication];
-            app.setMainMenu_(self.create_menu_bar(menus));
+            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+            app.setDelegate_(app_delegate);
+
+            let self_ptr = self as *const Self as *const c_void;
+            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+
+            let pool = NSAutoreleasePool::new(nil);
+            app.run();
+            pool.drain();
+
+            (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+            (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
         }
-        self
     }
 
-    fn run(self) {
+    fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
+        self.dispatcher.clone()
+    }
+
+    fn activate(&self, ignoring_other_apps: bool) {
         unsafe {
-            let self_ptr = Box::into_raw(Box::new(self));
+            let app = NSApplication::sharedApplication(nil);
+            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+        }
+    }
 
-            let pool = NSAutoreleasePool::new(nil);
-            let app: id = msg_send![APP_CLASS, sharedApplication];
-            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+    fn open_window(
+        &self,
+        options: platform::WindowOptions,
+        executor: Rc<executor::Foreground>,
+    ) -> Result<Box<dyn platform::Window>> {
+        Ok(Box::new(Window::open(options, executor, self.fonts())?))
+    }
 
-            (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
-            (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
-            app.setDelegate_(app_delegate);
-            app.run();
-            pool.drain();
+    fn prompt_for_paths(
+        &self,
+        options: platform::PathPromptOptions,
+    ) -> Option<Vec<std::path::PathBuf>> {
+        unsafe {
+            let panel = NSOpenPanel::openPanel(nil);
+            panel.setCanChooseDirectories_(options.directories.to_objc());
+            panel.setCanChooseFiles_(options.files.to_objc());
+            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+            panel.setResolvesAliases_(false.to_objc());
+            let response = panel.runModal();
+            if response == NSModalResponse::NSModalResponseOk {
+                let mut result = Vec::new();
+                let urls = panel.URLs();
+                for i in 0..urls.count() {
+                    let url = urls.objectAtIndex(i);
+                    let string = url.absoluteString();
+                    let string = std::ffi::CStr::from_ptr(string.UTF8String())
+                        .to_string_lossy()
+                        .to_string();
+                    if let Some(path) = string.strip_prefix("file://") {
+                        result.push(PathBuf::from(path));
+                    }
+                }
+                Some(result)
+            } else {
+                None
+            }
+        }
+    }
+
+    fn fonts(&self) -> Arc<dyn platform::FontSystem> {
+        self.fonts.clone()
+    }
+
+    fn quit(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, terminate: nil];
+        }
+    }
 
-            // The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
-            Box::from_raw(self_ptr);
+    fn copy(&self, text: &str) {
+        unsafe {
+            let data = NSData::dataWithBytes_length_(
+                nil,
+                text.as_ptr() as *const c_void,
+                text.len() as u64,
+            );
+            let pasteboard = NSPasteboard::generalPasteboard(nil);
+            pasteboard.clearContents();
+            pasteboard.setData_forType(data, NSPasteboardTypeString);
+        }
+    }
+
+    fn set_menus(&self, menus: &[Menu]) {
+        unsafe {
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            app.setMainMenu_(self.create_menu_bar(menus));
         }
     }
 }
 
-unsafe fn get_runner(object: &mut Object) -> &mut Runner {
-    let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
-    &mut *(runner_ptr as *mut Runner)
+unsafe fn get_platform(object: &mut Object) -> &MacPlatform {
+    let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+    assert!(!platform_ptr.is_null());
+    &*(platform_ptr as *const MacPlatform)
 }
 
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
-    let event = unsafe { Event::from_native(native_event, None) };
-
-    if let Some(event) = event {
-        let runner = unsafe { get_runner(this) };
-        if let Some(callback) = runner.event_callback.as_mut() {
-            if callback(event) {
-                return;
+    unsafe {
+        if let Some(event) = Event::from_native(native_event, None) {
+            let platform = get_platform(this);
+            if let Some(callback) = platform.callbacks.borrow_mut().event.as_mut() {
+                if callback(event) {
+                    return;
+                }
             }
         }
-    }
 
-    unsafe {
-        let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
+        msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
     }
 }
 
@@ -244,23 +326,23 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
         let app: id = msg_send![APP_CLASS, sharedApplication];
         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
 
-        let runner = get_runner(this);
-        if let Some(callback) = runner.finish_launching_callback.take() {
+        let platform = get_platform(this);
+        if let Some(callback) = platform.callbacks.borrow_mut().finish_launching.take() {
             callback();
         }
     }
 }
 
 extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-    let runner = unsafe { get_runner(this) };
-    if let Some(callback) = runner.become_active_callback.as_mut() {
+    let platform = unsafe { get_platform(this) };
+    if let Some(callback) = platform.callbacks.borrow_mut().become_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-    let runner = unsafe { get_runner(this) };
-    if let Some(callback) = runner.resign_active_callback.as_mut() {
+    let platform = unsafe { get_platform(this) };
+    if let Some(callback) = platform.callbacks.borrow_mut().resign_active.as_mut() {
         callback();
     }
 }
@@ -281,19 +363,19 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
             })
             .collect::<Vec<_>>()
     };
-    let runner = unsafe { get_runner(this) };
-    if let Some(callback) = runner.open_files_callback.as_mut() {
+    let platform = unsafe { get_platform(this) };
+    if let Some(callback) = platform.callbacks.borrow_mut().open_files.as_mut() {
         callback(paths);
     }
 }
 
 extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
     unsafe {
-        let runner = get_runner(this);
-        if let Some(callback) = runner.menu_command_callback.as_mut() {
+        let platform = get_platform(this);
+        if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() {
             let tag: NSInteger = msg_send![item, tag];
             let index = tag as usize;
-            if let Some(action) = runner.menu_item_actions.get(index) {
+            if let Some(action) = platform.menu_item_actions.borrow().get(index) {
                 callback(&action);
             }
         }

gpui/src/platform/mod.rs 🔗

@@ -22,19 +22,17 @@ use async_task::Runnable;
 pub use event::Event;
 use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc};
 
-pub trait Runner {
-    fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self;
-    fn on_menu_command<F: 'static + FnMut(&str)>(self, callback: F) -> Self;
-    fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
-    fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
-    fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
-    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
-    fn set_menus(self, menus: &[Menu]) -> Self;
-    fn run(self);
-}
+pub trait Platform {
+    fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>);
+    fn on_become_active(&self, callback: Box<dyn FnMut()>);
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+    fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
+    fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
 
-pub trait App {
     fn dispatcher(&self) -> Arc<dyn Dispatcher>;
+    fn fonts(&self) -> Arc<dyn FontSystem>;
+
     fn activate(&self, ignoring_other_apps: bool);
     fn open_window(
         &self,
@@ -42,9 +40,9 @@ pub trait App {
         executor: Rc<executor::Foreground>,
     ) -> Result<Box<dyn Window>>;
     fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
-    fn fonts(&self) -> Arc<dyn FontSystem>;
     fn quit(&self);
     fn copy(&self, text: &str);
+    fn set_menus(&self, menus: &[Menu]);
 }
 
 pub trait Dispatcher: Send + Sync {

gpui/src/platform/test.rs 🔗

@@ -2,7 +2,7 @@ use pathfinder_geometry::vector::Vector2F;
 use std::rc::Rc;
 use std::sync::Arc;
 
-struct App {
+struct Platform {
     dispatcher: Arc<dyn super::Dispatcher>,
     fonts: Arc<dyn super::FontSystem>,
 }
@@ -17,9 +17,7 @@ pub struct Window {
     resize_handlers: Vec<Box<dyn FnMut(&mut dyn super::WindowContext)>>,
 }
 
-pub struct WindowContext {}
-
-impl App {
+impl Platform {
     fn new() -> Self {
         Self {
             dispatcher: Arc::new(Dispatcher),
@@ -28,11 +26,29 @@ impl App {
     }
 }
 
-impl super::App for App {
+impl super::Platform for Platform {
+    fn on_menu_command(&self, _: Box<dyn FnMut(&str)>) {}
+
+    fn on_become_active(&self, _: Box<dyn FnMut()>) {}
+
+    fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
+
+    fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
+
+    fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
+
+    fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) {
+        unimplemented!()
+    }
+
     fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
         self.dispatcher.clone()
     }
 
+    fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
+        self.fonts.clone()
+    }
+
     fn activate(&self, _ignoring_other_apps: bool) {}
 
     fn open_window(
@@ -43,9 +59,7 @@ impl super::App for App {
         Ok(Box::new(Window::new(options.bounds.size())))
     }
 
-    fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
-        self.fonts.clone()
-    }
+    fn set_menus(&self, _menus: &[crate::Menu]) {}
 
     fn quit(&self) {}
 
@@ -102,6 +116,6 @@ impl super::Window for Window {
     }
 }
 
-pub fn app() -> impl super::App {
-    App::new()
+pub fn platform() -> impl super::Platform {
+    Platform::new()
 }

gpui/src/presenter.rs 🔗

@@ -69,14 +69,14 @@ impl Presenter {
         let mut scene = Scene::new(scale_factor);
 
         if let Some(root_view_id) = app.root_view_id(self.window_id) {
-            self.layout(window_size, app.downgrade());
+            self.layout(window_size, app.as_ref());
             self.after_layout(app);
             let mut ctx = PaintContext {
                 scene: &mut scene,
                 font_cache: &self.font_cache,
                 text_layout_cache: &self.text_layout_cache,
                 rendered_views: &mut self.rendered_views,
-                app: app.downgrade(),
+                app: app.as_ref(),
             };
             ctx.paint(root_view_id, Vector2F::zero());
             self.text_layout_cache.finish_frame();

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

@@ -2193,13 +2193,13 @@ mod tests {
 
     #[test]
     fn test_edit_events() {
-        App::test((), |mut app| async move {
+        App::test((), |app| {
             let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
             let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
 
             let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
             let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
-            let ops = buffer1.update(&mut app, |buffer, ctx| {
+            let ops = buffer1.update(app, |buffer, ctx| {
                 let buffer_1_events = buffer_1_events.clone();
                 ctx.subscribe(&buffer1, move |_, event, _| {
                     buffer_1_events.borrow_mut().push(event.clone())
@@ -2211,7 +2211,7 @@ mod tests {
 
                 buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap()
             });
-            buffer2.update(&mut app, |buffer, ctx| {
+            buffer2.update(app, |buffer, ctx| {
                 buffer.apply_ops(ops, Some(ctx)).unwrap();
             });
 
@@ -2715,12 +2715,12 @@ mod tests {
 
     #[test]
     fn test_is_modified() -> Result<()> {
-        App::test((), |mut app| async move {
+        App::test((), |app| {
             let model = app.add_model(|_| Buffer::new(0, "abc"));
             let events = Rc::new(RefCell::new(Vec::new()));
 
             // initially, the buffer isn't dirty.
-            model.update(&mut app, |buffer, ctx| {
+            model.update(app, |buffer, ctx| {
                 ctx.subscribe(&model, {
                     let events = events.clone();
                     move |_, event, _| events.borrow_mut().push(event.clone())
@@ -2733,7 +2733,7 @@ mod tests {
             });
 
             // after the first edit, the buffer is dirty, and emits a dirtied event.
-            model.update(&mut app, |buffer, ctx| {
+            model.update(app, |buffer, ctx| {
                 assert!(buffer.text() == "ac");
                 assert!(buffer.is_dirty());
                 assert_eq!(
@@ -2752,7 +2752,7 @@ mod tests {
             });
 
             // after saving, the buffer is not dirty, and emits a saved event.
-            model.update(&mut app, |buffer, ctx| {
+            model.update(app, |buffer, ctx| {
                 assert!(!buffer.is_dirty());
                 assert_eq!(*events.borrow(), &[Event::Saved]);
                 events.borrow_mut().clear();
@@ -2762,7 +2762,7 @@ mod tests {
             });
 
             // after editing again, the buffer is dirty, and emits another dirty event.
-            model.update(&mut app, |buffer, ctx| {
+            model.update(app, |buffer, ctx| {
                 assert!(buffer.text() == "aBDc");
                 assert!(buffer.is_dirty());
                 assert_eq!(
@@ -2788,7 +2788,7 @@ mod tests {
                 assert!(buffer.is_dirty());
             });
 
-            model.update(&mut app, |_, _| {
+            model.update(app, |_, _| {
                 assert_eq!(
                     *events.borrow(),
                     &[Event::Edited(vec![Edit {

zed/src/editor/buffer_element.rs 🔗

@@ -37,7 +37,7 @@ impl BufferElement {
         ctx: &mut EventContext,
     ) -> bool {
         if paint.text_bounds.contains_point(position) {
-            let view = self.view.as_ref(ctx.app);
+            let view = self.view.read(ctx.app);
             let position =
                 paint.point_for_position(view, layout, position, ctx.font_cache, ctx.app);
             ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
@@ -48,7 +48,7 @@ impl BufferElement {
     }
 
     fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext) -> bool {
-        if self.view.as_ref(ctx.app).is_selecting() {
+        if self.view.read(ctx.app).is_selecting() {
             ctx.dispatch_action("buffer:select", SelectAction::End);
             true
         } else {
@@ -63,7 +63,7 @@ impl BufferElement {
         paint: &mut PaintState,
         ctx: &mut EventContext,
     ) -> bool {
-        let view = self.view.as_ref(ctx.app);
+        let view = self.view.read(ctx.app);
 
         if view.is_selecting() {
             let rect = paint.text_bounds;
@@ -145,7 +145,7 @@ impl BufferElement {
             return false;
         }
 
-        let view = self.view.as_ref(ctx.app);
+        let view = self.view.read(ctx.app);
         let font_cache = &ctx.font_cache;
         let layout_cache = &ctx.text_layout_cache;
         let max_glyph_width = view.em_width(font_cache);
@@ -167,7 +167,7 @@ impl BufferElement {
     }
 
     fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
-        let view = self.view.as_ref(ctx.app);
+        let view = self.view.read(ctx.app);
         let line_height = view.line_height(ctx.font_cache);
         let scroll_top = view.scroll_position().y() * line_height;
 
@@ -197,7 +197,7 @@ impl BufferElement {
     }
 
     fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
-        let view = self.view.as_ref(ctx.app);
+        let view = self.view.read(ctx.app);
         let line_height = view.line_height(ctx.font_cache);
         let descent = view.font_descent(ctx.font_cache);
         let start_row = view.scroll_position().y() as u32;
@@ -313,14 +313,14 @@ impl Element for BufferElement {
         let app = ctx.app;
         let mut size = constraint.max;
         if size.y().is_infinite() {
-            let view = self.view.as_ref(app);
+            let view = self.view.read(app);
             size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache));
         }
         if size.x().is_infinite() {
             unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
         }
 
-        let view = self.view.as_ref(app);
+        let view = self.view.read(app);
         let font_cache = &ctx.font_cache;
         let layout_cache = &ctx.text_layout_cache;
         let line_height = view.line_height(font_cache);
@@ -402,9 +402,9 @@ impl Element for BufferElement {
         ctx: &mut AfterLayoutContext,
     ) {
         if let Some(layout) = layout {
-            let app = ctx.app.downgrade();
+            let app = ctx.app.as_ref();
 
-            let view = self.view.as_ref(app);
+            let view = self.view.read(app);
             view.clamp_scroll_left(
                 layout
                     .scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app)
@@ -437,7 +437,7 @@ impl Element for BufferElement {
                 layout.text_size,
             );
 
-            if self.view.as_ref(ctx.app).is_gutter_visible() {
+            if self.view.read(ctx.app).is_gutter_visible() {
                 self.paint_gutter(gutter_bounds, layout, ctx);
             }
             self.paint_text(text_bounds, layout, ctx);

zed/src/editor/buffer_view.rs 🔗

@@ -6,8 +6,9 @@ use crate::{settings::Settings, watch, workspace};
 use anyhow::Result;
 use futures_core::future::LocalBoxFuture;
 use gpui::{
-    fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element,
-    ElementBox, Entity, FontCache, ModelHandle, View, ViewContext, WeakViewHandle,
+    fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element,
+    ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
+    WeakViewHandle,
 };
 use gpui::{geometry::vector::Vector2F, TextLayoutCache};
 use parking_lot::Mutex;
@@ -24,7 +25,7 @@ use std::{
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
     app.add_bindings(vec![
         Binding::new("backspace", "buffer:backspace", Some("BufferView")),
         Binding::new("enter", "buffer:newline", Some("BufferView")),
@@ -124,7 +125,7 @@ impl BufferView {
         });
         ctx.observe(&display_map, Self::on_display_map_changed);
 
-        let buffer_ref = buffer.as_ref(ctx);
+        let buffer_ref = buffer.read(ctx);
         Self {
             handle: ctx.handle().downgrade(),
             buffer,
@@ -187,7 +188,7 @@ impl BufferView {
             return false;
         }
 
-        let map = self.display_map.as_ref(app);
+        let map = self.display_map.read(app);
         let visible_lines = viewport_height / line_height;
         let first_cursor_top = self
             .selections
@@ -237,7 +238,7 @@ impl BufferView {
         layouts: &[Arc<text_layout::Line>],
         app: &AppContext,
     ) {
-        let map = self.display_map.as_ref(app);
+        let map = self.display_map.read(app);
 
         let mut target_left = std::f32::INFINITY;
         let mut target_right = 0.0_f32;
@@ -286,7 +287,7 @@ impl BufferView {
             ctx.emit(Event::Activate);
         }
 
-        let display_map = self.display_map.as_ref(ctx);
+        let display_map = self.display_map.read(ctx);
         let cursor = display_map
             .anchor_before(position, Bias::Left, ctx.app())
             .unwrap();
@@ -311,8 +312,8 @@ impl BufferView {
         scroll_position: Vector2F,
         ctx: &mut ViewContext<Self>,
     ) {
-        let buffer = self.buffer.as_ref(ctx);
-        let map = self.display_map.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
+        let map = self.display_map.read(ctx);
         let cursor = map.anchor_before(position, Bias::Left, ctx.app()).unwrap();
         if let Some(selection) = self.pending_selection.as_mut() {
             selection.set_head(buffer, cursor);
@@ -346,8 +347,8 @@ impl BufferView {
     where
         T: IntoIterator<Item = &'a Range<DisplayPoint>>,
     {
-        let buffer = self.buffer.as_ref(ctx);
-        let map = self.display_map.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
+        let map = self.display_map.read(ctx);
         let mut selections = Vec::new();
         for range in ranges {
             selections.push(Selection {
@@ -365,7 +366,7 @@ impl BufferView {
     }
 
     fn insert(&mut self, text: &String, ctx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
         let mut offset_ranges = SmallVec::<[Range<usize>; 32]>::new();
         for selection in &self.selections {
             let start = selection.start.to_offset(buffer).unwrap();
@@ -380,7 +381,7 @@ impl BufferView {
             };
         });
 
-        let buffer = self.buffer.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
         let char_count = text.chars().count() as isize;
         let mut delta = 0_isize;
         self.selections = offset_ranges
@@ -415,8 +416,8 @@ impl BufferView {
     }
 
     pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
-        let buffer = self.buffer.as_ref(ctx);
-        let map = self.display_map.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
+        let map = self.display_map.read(ctx);
         for selection in &mut self.selections {
             if selection.range(buffer).is_empty() {
                 let head = selection.head().to_display_point(map, ctx.app()).unwrap();
@@ -438,7 +439,7 @@ impl BufferView {
     pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         {
             let app = ctx.app();
-            let map = self.display_map.as_ref(ctx);
+            let map = self.display_map.read(ctx);
             for selection in &mut self.selections {
                 let start = selection.start.to_display_point(map, app).unwrap();
                 let end = selection.end.to_display_point(map, app).unwrap();
@@ -461,8 +462,8 @@ impl BufferView {
 
     pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         {
-            let buffer = self.buffer.as_ref(ctx);
-            let map = self.display_map.as_ref(ctx);
+            let buffer = self.buffer.read(ctx);
+            let map = self.display_map.read(ctx);
             for selection in &mut self.selections {
                 let head = selection.head().to_display_point(map, ctx.app()).unwrap();
                 let cursor = map
@@ -482,7 +483,7 @@ impl BufferView {
     pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         {
             let app = ctx.app();
-            let map = self.display_map.as_ref(app);
+            let map = self.display_map.read(app);
             for selection in &mut self.selections {
                 let start = selection.start.to_display_point(map, app).unwrap();
                 let end = selection.end.to_display_point(map, app).unwrap();
@@ -505,9 +506,9 @@ impl BufferView {
 
     pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         {
-            let buffer = self.buffer.as_ref(ctx);
+            let buffer = self.buffer.read(ctx);
             let app = ctx.app();
-            let map = self.display_map.as_ref(app);
+            let map = self.display_map.read(app);
             for selection in &mut self.selections {
                 let head = selection.head().to_display_point(map, ctx.app()).unwrap();
                 let cursor = map
@@ -525,7 +526,7 @@ impl BufferView {
             ctx.propagate_action();
         } else {
             let app = ctx.app();
-            let map = self.display_map.as_ref(app);
+            let map = self.display_map.read(app);
             for selection in &mut self.selections {
                 let start = selection.start.to_display_point(map, app).unwrap();
                 let end = selection.end.to_display_point(map, app).unwrap();
@@ -550,8 +551,8 @@ impl BufferView {
             ctx.propagate_action();
         } else {
             let app = ctx.app();
-            let buffer = self.buffer.as_ref(app);
-            let map = self.display_map.as_ref(app);
+            let buffer = self.buffer.read(app);
+            let map = self.display_map.read(app);
             for selection in &mut self.selections {
                 let head = selection.head().to_display_point(map, app).unwrap();
                 let (head, goal_column) =
@@ -568,7 +569,7 @@ impl BufferView {
             ctx.propagate_action();
         } else {
             let app = ctx.app();
-            let map = self.display_map.as_ref(app);
+            let map = self.display_map.read(app);
             for selection in &mut self.selections {
                 let start = selection.start.to_display_point(map, app).unwrap();
                 let end = selection.end.to_display_point(map, app).unwrap();
@@ -593,8 +594,8 @@ impl BufferView {
             ctx.propagate_action();
         } else {
             let app = ctx.app();
-            let buffer = self.buffer.as_ref(ctx);
-            let map = self.display_map.as_ref(ctx);
+            let buffer = self.buffer.read(ctx);
+            let map = self.display_map.read(ctx);
             for selection in &mut self.selections {
                 let head = selection.head().to_display_point(map, app).unwrap();
                 let (head, goal_column) =
@@ -614,7 +615,7 @@ impl BufferView {
     }
 
     fn merge_selections(&mut self, ctx: &AppContext) {
-        let buffer = self.buffer.as_ref(ctx);
+        let buffer = self.buffer.read(ctx);
         let mut i = 1;
         while i < self.selections.len() {
             if self.selections[i - 1]
@@ -650,14 +651,14 @@ impl BufferView {
         self.selections
             .first()
             .unwrap()
-            .display_range(self.display_map.as_ref(app), app)
+            .display_range(self.display_map.read(app), app)
     }
 
     pub fn last_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
         self.selections
             .last()
             .unwrap()
-            .display_range(self.display_map.as_ref(app), app)
+            .display_range(self.display_map.read(app), app)
     }
 
     pub fn selections_in_range<'a>(
@@ -665,7 +666,7 @@ impl BufferView {
         range: Range<DisplayPoint>,
         app: &'a AppContext,
     ) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
-        let map = self.display_map.as_ref(app);
+        let map = self.display_map.read(app);
 
         let start = map.anchor_before(range.start, Bias::Left, app).unwrap();
         let start_index = self.selection_insertion_index(&start, app);
@@ -685,7 +686,7 @@ impl BufferView {
     }
 
     fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
-        let buffer = self.buffer.as_ref(app);
+        let buffer = self.buffer.read(app);
 
         match self
             .selections
@@ -719,7 +720,7 @@ impl BufferView {
         let mut fold_ranges = Vec::new();
 
         let app = ctx.app();
-        let map = self.display_map.as_ref(app);
+        let map = self.display_map.read(app);
         for selection in &self.selections {
             let (start, end) = selection.display_range(map, app).sorted();
             let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row;
@@ -749,8 +750,8 @@ impl BufferView {
         use super::RangeExt;
 
         let app = ctx.app();
-        let map = self.display_map.as_ref(app);
-        let buffer = self.buffer.as_ref(app);
+        let map = self.display_map.read(app);
+        let buffer = self.buffer.read(app);
         let ranges = self
             .selections
             .iter()
@@ -795,7 +796,7 @@ impl BufferView {
         let mut is_blank = true;
         for c in self
             .display_map
-            .as_ref(app)
+            .read(app)
             .chars_at(DisplayPoint::new(display_row, 0), app)?
         {
             if c == ' ' {
@@ -809,7 +810,7 @@ impl BufferView {
     }
 
     fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result<Range<Point>> {
-        let map = self.display_map.as_ref(app);
+        let map = self.display_map.read(app);
         let max_point = self.max_point(app);
 
         let (start_indent, _) = self.line_indent(start_row, app)?;
@@ -830,7 +831,7 @@ impl BufferView {
 
     pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         self.display_map.update(ctx, |map, ctx| {
-            let buffer = self.buffer.as_ref(ctx);
+            let buffer = self.buffer.read(ctx);
             let ranges = self
                 .selections
                 .iter()
@@ -841,23 +842,23 @@ impl BufferView {
     }
 
     pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
-        self.display_map.as_ref(app).line(display_row, app)
+        self.display_map.read(app).line(display_row, app)
     }
 
     pub fn line_len(&self, display_row: u32, app: &AppContext) -> Result<u32> {
-        self.display_map.as_ref(app).line_len(display_row, app)
+        self.display_map.read(app).line_len(display_row, app)
     }
 
     pub fn rightmost_point(&self, app: &AppContext) -> DisplayPoint {
-        self.display_map.as_ref(app).rightmost_point()
+        self.display_map.read(app).rightmost_point()
     }
 
     pub fn max_point(&self, app: &AppContext) -> DisplayPoint {
-        self.display_map.as_ref(app).max_point(app)
+        self.display_map.read(app).max_point(app)
     }
 
     pub fn text(&self, app: &AppContext) -> String {
-        self.display_map.as_ref(app).text(app)
+        self.display_map.read(app).text(app)
     }
 
     pub fn font_size(&self) -> f32 {
@@ -901,7 +902,7 @@ impl BufferView {
         let font_size = settings.buffer_font_size;
         let font_id =
             font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
-        let digit_count = ((self.buffer.as_ref(app).max_point().row + 1) as f32)
+        let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32)
             .log10()
             .floor() as usize
             + 1;
@@ -922,7 +923,7 @@ impl BufferView {
         layout_cache: &TextLayoutCache,
         app: &AppContext,
     ) -> Result<Vec<Arc<text_layout::Line>>> {
-        let display_map = self.display_map.as_ref(app);
+        let display_map = self.display_map.read(app);
 
         let settings = smol::block_on(self.settings.read());
         let font_size = settings.buffer_font_size;
@@ -958,7 +959,7 @@ impl BufferView {
         layout_cache: &TextLayoutCache,
         app: &AppContext,
     ) -> Result<Vec<Arc<text_layout::Line>>> {
-        let display_map = self.display_map.as_ref(app);
+        let display_map = self.display_map.read(app);
 
         rows.end = cmp::min(rows.end, display_map.max_point(app).row() + 1);
         if rows.start >= rows.end {
@@ -1148,7 +1149,7 @@ impl workspace::ItemView for BufferView {
     }
 
     fn title(&self, app: &AppContext) -> std::string::String {
-        if let Some(path) = self.buffer.as_ref(app).path(app) {
+        if let Some(path) = self.buffer.read(app).path(app) {
             path.file_name()
                 .expect("buffer's path is always to a file")
                 .to_string_lossy()
@@ -1159,7 +1160,7 @@ impl workspace::ItemView for BufferView {
     }
 
     fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
-        self.buffer.as_ref(app).entry_id()
+        self.buffer.read(app).entry_id()
     }
 
     fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
@@ -1176,7 +1177,7 @@ impl workspace::ItemView for BufferView {
     }
 
     fn is_dirty(&self, ctx: &AppContext) -> bool {
-        self.buffer.as_ref(ctx).is_dirty()
+        self.buffer.read(ctx).is_dirty()
     }
 }
 
@@ -1239,112 +1240,125 @@ mod tests {
     use super::*;
     use crate::{editor::Point, settings, test::sample_text};
     use anyhow::Error;
+    use gpui::App;
     use unindent::Unindent;
 
     #[test]
     fn test_selection_with_mouse() {
-        App::test((), |mut app| async move {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let (_, buffer_view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+            );
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+            );
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+            );
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.end_selection(ctx);
                 view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+            );
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
                 view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [
-                        DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
-                        DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
-                    ]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [
+                    DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
+                    DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+                ]
+            );
 
-            buffer_view.update(&mut app, |view, ctx| {
+            buffer_view.update(app, |view, ctx| {
                 view.end_selection(ctx);
             });
 
-            buffer_view.read(&app, |view, app| {
-                let selections = view
-                    .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
-                    .collect::<Vec<_>>();
-                assert_eq!(
-                    selections,
-                    [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
-                );
-            });
+            let view = buffer_view.read(app);
+            let selections = view
+                .selections_in_range(
+                    DisplayPoint::zero()..view.max_point(app.as_ref()),
+                    app.as_ref(),
+                )
+                .collect::<Vec<_>>();
+            assert_eq!(
+                selections,
+                [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+            );
         });
     }
 
     #[test]
-    fn test_layout_line_numbers() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_layout_line_numbers() {
+        App::test((), |app| {
             let layout_cache = TextLayoutCache::new(app.platform().fonts());
-            let font_cache = app.font_cache();
+            let font_cache = app.font_cache().clone();
 
             let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
 
@@ -1352,19 +1366,17 @@ mod tests {
             let (_, view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
 
-            view.read(&app, |view, app| {
-                let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?;
-                assert_eq!(layouts.len(), 6);
-                Result::<()>::Ok(())
-            })?;
-
-            Ok(())
+            let layouts = view
+                .read(app)
+                .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
+                .unwrap();
+            assert_eq!(layouts.len(), 6);
         })
     }
 
     #[test]
-    fn test_fold() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_fold() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| {
                 Buffer::new(
                     0,
@@ -1392,8 +1404,9 @@ mod tests {
             let (_, view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
 
-            view.update(&mut app, |view, ctx| {
-                view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)?;
+            view.update(app, |view, ctx| {
+                view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
+                    .unwrap();
                 view.fold(&(), ctx);
                 assert_eq!(
                     view.text(ctx.app()),
@@ -1447,24 +1460,20 @@ mod tests {
                 );
 
                 view.unfold(&(), ctx);
-                assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text());
-
-                Ok::<(), Error>(())
-            })?;
-
-            Ok(())
-        })
+                assert_eq!(view.text(ctx.app()), buffer.read(ctx).text());
+            });
+        });
     }
 
     #[test]
     fn test_move_cursor() -> Result<()> {
-        App::test((), |mut app| async move {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let (_, view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
 
-            buffer.update(&mut app, |buffer, ctx| {
+            buffer.update(app, |buffer, ctx| {
                 buffer.edit(
                     vec![
                         Point::new(1, 0)..Point::new(1, 0),
@@ -1475,7 +1484,7 @@ mod tests {
                 )
             })?;
 
-            view.update(&mut app, |view, ctx| {
+            view.update(app, |view, ctx| {
                 view.move_down(&(), ctx);
                 assert_eq!(
                     view.selections(ctx.app()),
@@ -1494,8 +1503,8 @@ mod tests {
     }
 
     #[test]
-    fn test_backspace() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_backspace() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| {
                 Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
             });
@@ -1503,7 +1512,7 @@ mod tests {
             let (_, view) =
                 app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
 
-            view.update(&mut app, |view, ctx| -> Result<()> {
+            view.update(app, |view, ctx| {
                 view.select_ranges(
                     &[
                         // an empty selection - the preceding character is deleted
@@ -1514,17 +1523,15 @@ mod tests {
                         DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
                     ],
                     ctx,
-                )?;
+                )
+                .unwrap();
                 view.backspace(&(), ctx);
-                Ok(())
-            })?;
-
-            buffer.read(&mut app, |buffer, _| -> Result<()> {
-                assert_eq!(buffer.text(), "oe two three\nfou five six\nseven ten\n");
-                Ok(())
-            })?;
+            });
 
-            Ok(())
+            assert_eq!(
+                buffer.read(app).text(),
+                "oe two three\nfou five six\nseven ten\n"
+            );
         })
     }
 

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

@@ -22,7 +22,7 @@ pub struct FoldMap {
 
 impl FoldMap {
     pub fn new(buffer: ModelHandle<Buffer>, app: &AppContext) -> Self {
-        let text_summary = buffer.as_ref(app).text_summary();
+        let text_summary = buffer.read(app).text_summary();
         Self {
             buffer,
             folds: Vec::new(),
@@ -72,7 +72,7 @@ impl FoldMap {
         let offset = self.to_display_offset(point, app)?;
         let mut cursor = self.transforms.cursor();
         cursor.seek(&offset, SeekBias::Right);
-        let buffer = self.buffer.as_ref(app);
+        let buffer = self.buffer.read(app);
         Ok(Chars {
             cursor,
             offset: offset.0,
@@ -95,7 +95,7 @@ impl FoldMap {
         app: &AppContext,
     ) -> Result<()> {
         let mut edits = Vec::new();
-        let buffer = self.buffer.as_ref(app);
+        let buffer = self.buffer.read(app);
         for range in ranges.into_iter() {
             let start = range.start.to_offset(buffer)?;
             let end = range.end.to_offset(buffer)?;
@@ -124,7 +124,7 @@ impl FoldMap {
         ranges: impl IntoIterator<Item = Range<T>>,
         app: &AppContext,
     ) -> Result<()> {
-        let buffer = self.buffer.as_ref(app);
+        let buffer = self.buffer.read(app);
 
         let mut edits = Vec::new();
         for range in ranges.into_iter() {
@@ -184,7 +184,7 @@ impl FoldMap {
                 .ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
             assert!(transform.display_text.is_none());
             let end_buffer_offset =
-                (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.as_ref(app))?;
+                (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))?;
             offset += end_buffer_offset - cursor.start().buffer.chars;
         }
         Ok(DisplayOffset(offset))
@@ -208,7 +208,7 @@ impl FoldMap {
     }
 
     pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> {
-        let buffer = self.buffer.as_ref(app);
+        let buffer = self.buffer.read(app);
         let mut edits = edits.iter().cloned().peekable();
 
         let mut new_transforms = SumTree::new();
@@ -469,115 +469,106 @@ mod tests {
     use gpui::App;
 
     #[test]
-    fn test_basic_folds() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_basic_folds() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
-            let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
-
-            app.read(|app| {
-                map.fold(
-                    vec![
-                        Point::new(0, 2)..Point::new(2, 2),
-                        Point::new(2, 4)..Point::new(4, 1),
-                    ],
-                    app,
-                )?;
-                assert_eq!(map.text(app), "aa…cc…eeeee");
-                Ok::<(), anyhow::Error>(())
-            })?;
-
-            let edits = buffer.update(&mut app, |buffer, ctx| {
+            let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+            map.fold(
+                vec![
+                    Point::new(0, 2)..Point::new(2, 2),
+                    Point::new(2, 4)..Point::new(4, 1),
+                ],
+                app.as_ref(),
+            )
+            .unwrap();
+            assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
+
+            let edits = buffer.update(app, |buffer, ctx| {
                 let start_version = buffer.version.clone();
-                buffer.edit(
-                    vec![
-                        Point::new(0, 0)..Point::new(0, 1),
-                        Point::new(2, 3)..Point::new(2, 3),
-                    ],
-                    "123",
-                    Some(ctx),
-                )?;
-                Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
-            })?;
-
-            app.read(|app| {
-                map.apply_edits(&edits, app)?;
-                assert_eq!(map.text(app), "123a…c123c…eeeee");
-                Ok::<(), anyhow::Error>(())
-            })?;
-
-            let edits = buffer.update(&mut app, |buffer, ctx| {
-                let start_version = buffer.version.clone();
-                buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))?;
-                Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
-            })?;
+                buffer
+                    .edit(
+                        vec![
+                            Point::new(0, 0)..Point::new(0, 1),
+                            Point::new(2, 3)..Point::new(2, 3),
+                        ],
+                        "123",
+                        Some(ctx),
+                    )
+                    .unwrap();
+                buffer.edits_since(start_version).collect::<Vec<_>>()
+            });
 
-            app.read(|app| {
-                map.apply_edits(&edits, app)?;
-                assert_eq!(map.text(app), "123a…c123456eee");
+            map.apply_edits(&edits, app.as_ref()).unwrap();
+            assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
+
+            let edits = buffer.update(app, |buffer, ctx| {
+                let start_version = buffer.version.clone();
+                buffer
+                    .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
+                    .unwrap();
+                buffer.edits_since(start_version).collect::<Vec<_>>()
+            });
 
-                map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?;
-                assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee");
+            map.apply_edits(&edits, app.as_ref()).unwrap();
+            assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
 
-                Ok(())
-            })
-        })
+            map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
+                .unwrap();
+            assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
+        });
     }
 
     #[test]
-    fn test_overlapping_folds() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_overlapping_folds() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
-            app.read(|app| {
-                let mut map = FoldMap::new(buffer.clone(), app);
-                map.fold(
-                    vec![
-                        Point::new(0, 2)..Point::new(2, 2),
-                        Point::new(0, 4)..Point::new(1, 0),
-                        Point::new(1, 2)..Point::new(3, 2),
-                        Point::new(3, 1)..Point::new(4, 1),
-                    ],
-                    app,
-                )?;
-                assert_eq!(map.text(app), "aa…eeeee");
-                Ok(())
-            })
+            let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+            map.fold(
+                vec![
+                    Point::new(0, 2)..Point::new(2, 2),
+                    Point::new(0, 4)..Point::new(1, 0),
+                    Point::new(1, 2)..Point::new(3, 2),
+                    Point::new(3, 1)..Point::new(4, 1),
+                ],
+                app.as_ref(),
+            )
+            .unwrap();
+            assert_eq!(map.text(app.as_ref()), "aa…eeeee");
         })
     }
 
     #[test]
-    fn test_merging_folds_via_edit() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_merging_folds_via_edit() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
-            let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
-
-            app.read(|app| {
-                map.fold(
-                    vec![
-                        Point::new(0, 2)..Point::new(2, 2),
-                        Point::new(3, 1)..Point::new(4, 1),
-                    ],
-                    app,
-                )?;
-                assert_eq!(map.text(app), "aa…cccc\nd…eeeee");
-                Ok::<(), anyhow::Error>(())
-            })?;
-
-            let edits = buffer.update(&mut app, |buffer, ctx| {
+            let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+            map.fold(
+                vec![
+                    Point::new(0, 2)..Point::new(2, 2),
+                    Point::new(3, 1)..Point::new(4, 1),
+                ],
+                app.as_ref(),
+            )
+            .unwrap();
+            assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
+
+            let edits = buffer.update(app, |buffer, ctx| {
                 let start_version = buffer.version.clone();
-                buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))?;
-                Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
-            })?;
-
-            app.read(|app| {
-                map.apply_edits(&edits, app)?;
-                assert_eq!(map.text(app), "aa…eeeee");
-                Ok(())
-            })
-        })
+                buffer
+                    .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
+                    .unwrap();
+                buffer.edits_since(start_version).collect::<Vec<_>>()
+            });
+
+            map.apply_edits(&edits, app.as_ref()).unwrap();
+            assert_eq!(map.text(app.as_ref()), "aa…eeeee");
+        });
     }
 
     #[test]
-    fn test_random_folds() -> Result<()> {
+    fn test_random_folds() {
         use crate::editor::ToPoint;
         use crate::util::RandomCharIter;
         use rand::prelude::*;
@@ -597,16 +588,16 @@ mod tests {
             println!("{:?}", seed);
             let mut rng = StdRng::seed_from_u64(seed);
 
-            App::test((), |mut app| async move {
+            App::test((), |app| {
                 let buffer = app.add_model(|_| {
                     let len = rng.gen_range(0..10);
                     let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
                     Buffer::new(0, text)
                 });
-                let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
+                let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 
-                app.read(|app| {
-                    let buffer = buffer.as_ref(app);
+                {
+                    let buffer = buffer.read(app);
 
                     let fold_count = rng.gen_range(0..10);
                     let mut fold_ranges: Vec<Range<usize>> = Vec::new();
@@ -616,93 +607,83 @@ mod tests {
                         fold_ranges.push(start..end);
                     }
 
-                    map.fold(fold_ranges, app)?;
+                    map.fold(fold_ranges, app.as_ref()).unwrap();
 
                     let mut expected_text = buffer.text();
-                    for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
+                    for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
                         expected_text.replace_range(fold_range.start..fold_range.end, "…");
                     }
 
-                    assert_eq!(map.text(app), expected_text);
+                    assert_eq!(map.text(app.as_ref()), expected_text);
 
-                    for fold_range in map.merged_fold_ranges(app) {
+                    for fold_range in map.merged_fold_ranges(app.as_ref()) {
                         let display_point =
                             map.to_display_point(fold_range.start.to_point(buffer).unwrap());
                         assert!(map.is_line_folded(display_point.row()));
                     }
+                }
 
-                    Ok::<(), anyhow::Error>(())
-                })?;
-
-                let edits = buffer.update(&mut app, |buffer, ctx| {
+                let edits = buffer.update(app, |buffer, ctx| {
                     let start_version = buffer.version.clone();
                     let edit_count = rng.gen_range(1..10);
                     buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
-                    Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
-                })?;
-
-                app.read(|app| {
-                    map.apply_edits(&edits, app)?;
-
-                    let buffer = map.buffer.as_ref(app);
-                    let mut expected_text = buffer.text();
-                    let mut expected_buffer_rows = Vec::new();
-                    let mut next_row = buffer.max_point().row;
-                    for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
-                        let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
-                        let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
-                        expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
-                        next_row = fold_start.row;
+                    buffer.edits_since(start_version).collect::<Vec<_>>()
+                });
 
-                        expected_text.replace_range(fold_range.start..fold_range.end, "…");
-                    }
-                    expected_buffer_rows.extend((0..=next_row).rev());
-                    expected_buffer_rows.reverse();
+                map.apply_edits(&edits, app.as_ref()).unwrap();
 
-                    assert_eq!(map.text(app), expected_text);
+                let buffer = map.buffer.read(app);
+                let mut expected_text = buffer.text();
+                let mut expected_buffer_rows = Vec::new();
+                let mut next_row = buffer.max_point().row;
+                for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
+                    let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
+                    let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
+                    expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
+                    next_row = fold_start.row;
 
-                    for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
-                        let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
-                        assert_eq!(
-                            map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
-                            expected_buffer_rows[idx..],
-                        );
-                    }
+                    expected_text.replace_range(fold_range.start..fold_range.end, "…");
+                }
+                expected_buffer_rows.extend((0..=next_row).rev());
+                expected_buffer_rows.reverse();
 
-                    Ok::<(), anyhow::Error>(())
-                })?;
+                assert_eq!(map.text(app.as_ref()), expected_text);
 
-                Ok::<(), anyhow::Error>(())
-            })?;
+                for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
+                    let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
+                    assert_eq!(
+                        map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
+                        expected_buffer_rows[idx..],
+                    );
+                }
+            });
         }
-
-        Ok(())
     }
 
     #[test]
-    fn test_buffer_rows() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_buffer_rows() {
+        App::test((), |app| {
             let text = sample_text(6, 6) + "\n";
             let buffer = app.add_model(|_| Buffer::new(0, text));
 
-            app.read(|app| {
-                let mut map = FoldMap::new(buffer.clone(), app);
-
-                map.fold(
-                    vec![
-                        Point::new(0, 2)..Point::new(2, 2),
-                        Point::new(3, 1)..Point::new(4, 1),
-                    ],
-                    app,
-                )?;
-
-                assert_eq!(map.text(app), "aa…cccc\nd…eeeee\nffffff\n");
-                assert_eq!(map.buffer_rows(0)?.collect::<Vec<_>>(), vec![0, 3, 5, 6]);
-                assert_eq!(map.buffer_rows(3)?.collect::<Vec<_>>(), vec![6]);
-
-                Ok(())
-            })
-        })
+            let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+            map.fold(
+                vec![
+                    Point::new(0, 2)..Point::new(2, 2),
+                    Point::new(3, 1)..Point::new(4, 1),
+                ],
+                app.as_ref(),
+            )
+            .unwrap();
+
+            assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
+            assert_eq!(
+                map.buffer_rows(0).unwrap().collect::<Vec<_>>(),
+                vec![0, 3, 5, 6]
+            );
+            assert_eq!(map.buffer_rows(3).unwrap().collect::<Vec<_>>(), vec![6]);
+        });
     }
 
     impl FoldMap {
@@ -713,7 +694,7 @@ mod tests {
         }
 
         fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
-            let buffer = self.buffer.as_ref(app);
+            let buffer = self.buffer.read(app);
             let mut fold_ranges = self
                 .folds
                 .iter()

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

@@ -108,7 +108,7 @@ impl DisplayMap {
         app: &AppContext,
     ) -> Result<Anchor> {
         self.buffer
-            .as_ref(app)
+            .read(app)
             .anchor_before(point.to_buffer_point(self, bias, app)?)
     }
 
@@ -119,7 +119,7 @@ impl DisplayMap {
         app: &AppContext,
     ) -> Result<Anchor> {
         self.buffer
-            .as_ref(app)
+            .read(app)
             .anchor_after(point.to_buffer_point(self, bias, app)?)
     }
 
@@ -206,7 +206,7 @@ impl Point {
 
 impl Anchor {
     pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
-        self.to_point(map.buffer.as_ref(app))?
+        self.to_point(map.buffer.read(app))?
             .to_display_point(map, app)
     }
 }
@@ -292,52 +292,51 @@ pub fn collapse_tabs(
 mod tests {
     use super::*;
     use crate::test::*;
-    use anyhow::Error;
     use gpui::App;
 
     #[test]
-    fn test_chars_at() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_chars_at() {
+        App::test((), |app| {
             let text = sample_text(6, 6);
             let buffer = app.add_model(|_| Buffer::new(0, text));
             let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
-            buffer.update(&mut app, |buffer, ctx| {
-                buffer.edit(
-                    vec![
-                        Point::new(1, 0)..Point::new(1, 0),
-                        Point::new(1, 1)..Point::new(1, 1),
-                        Point::new(2, 1)..Point::new(2, 1),
-                    ],
-                    "\t",
-                    Some(ctx),
-                )
-            })?;
-
-            map.read(&app, |map, ctx| {
-                assert_eq!(
-                    map.chars_at(DisplayPoint::new(1, 0), ctx)?
-                        .take(10)
-                        .collect::<String>(),
-                    "    b   bb"
-                );
-                assert_eq!(
-                    map.chars_at(DisplayPoint::new(1, 2), ctx)?
-                        .take(10)
-                        .collect::<String>(),
-                    "  b   bbbb"
-                );
-                assert_eq!(
-                    map.chars_at(DisplayPoint::new(1, 6), ctx)?
-                        .take(13)
-                        .collect::<String>(),
-                    "  bbbbb\nc   c"
-                );
-
-                Ok::<(), Error>(())
-            })?;
-
-            Ok(())
-        })
+            buffer
+                .update(app, |buffer, ctx| {
+                    buffer.edit(
+                        vec![
+                            Point::new(1, 0)..Point::new(1, 0),
+                            Point::new(1, 1)..Point::new(1, 1),
+                            Point::new(2, 1)..Point::new(2, 1),
+                        ],
+                        "\t",
+                        Some(ctx),
+                    )
+                })
+                .unwrap();
+
+            let map = map.read(app);
+            assert_eq!(
+                map.chars_at(DisplayPoint::new(1, 0), app.as_ref())
+                    .unwrap()
+                    .take(10)
+                    .collect::<String>(),
+                "    b   bb"
+            );
+            assert_eq!(
+                map.chars_at(DisplayPoint::new(1, 2), app.as_ref())
+                    .unwrap()
+                    .take(10)
+                    .collect::<String>(),
+                "  b   bbbb"
+            );
+            assert_eq!(
+                map.chars_at(DisplayPoint::new(1, 6), app.as_ref())
+                    .unwrap()
+                    .take(13)
+                    .collect::<String>(),
+                "  bbbbb\nc   c"
+            );
+        });
     }
 
     #[test]
@@ -364,14 +363,14 @@ mod tests {
     }
 
     #[test]
-    fn test_max_point() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_max_point() {
+        App::test((), |app| {
             let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
             let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
-            map.read(&app, |map, app| {
-                assert_eq!(map.max_point(app), DisplayPoint::new(1, 11))
-            });
-            Ok(())
-        })
+            assert_eq!(
+                map.read(app).max_point(app.as_ref()),
+                DisplayPoint::new(1, 11)
+            )
+        });
     }
 }

zed/src/file_finder.rs 🔗

@@ -11,8 +11,8 @@ use gpui::{
     fonts::{Properties, Weight},
     geometry::vector::vec2f,
     keymap::{self, Binding},
-    App, AppContext, Axis, Border, Entity, ModelHandle, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
+    ViewHandle, WeakViewHandle,
 };
 use std::cmp;
 
@@ -28,7 +28,7 @@ pub struct FileFinder {
     list_state: UniformListState,
 }
 
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
     app.add_action("file_finder:toggle", FileFinder::toggle);
     app.add_action("file_finder:confirm", FileFinder::confirm);
     app.add_action("file_finder:select", FileFinder::select);
@@ -114,7 +114,7 @@ impl FileFinder {
             self.matches.len(),
             move |mut range, items, app| {
                 let finder = handle.upgrade(app).unwrap();
-                let finder = finder.as_ref(app);
+                let finder = finder.read(app);
                 let start = range.start;
                 range.end = cmp::min(range.end, finder.matches.len());
                 items.extend(finder.matches[range].iter().enumerate().filter_map(
@@ -287,7 +287,7 @@ impl FileFinder {
     }
 
     fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
-        self.spawn_search(self.query_buffer.as_ref(ctx).text(ctx.app()), ctx);
+        self.spawn_search(self.query_buffer.read(ctx).text(ctx.app()), ctx);
     }
 
     fn on_query_buffer_event(
@@ -299,7 +299,7 @@ impl FileFinder {
         use buffer_view::Event::*;
         match event {
             Edited => {
-                let query = self.query_buffer.as_ref(ctx).text(ctx.app());
+                let query = self.query_buffer.read(ctx).text(ctx.app());
                 if query.is_empty() {
                     self.latest_search_id = util::post_inc(&mut self.search_count);
                     self.matches.clear();
@@ -371,18 +371,18 @@ impl FileFinder {
 
     fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> {
         self.workspace
-            .as_ref(app)
+            .read(app)
             .worktrees()
             .get(&tree_id)
-            .map(|worktree| worktree.as_ref(app))
+            .map(|worktree| worktree.read(app))
     }
 
     fn worktrees(&self, app: &AppContext) -> Vec<Worktree> {
         self.workspace
-            .as_ref(app)
+            .read(app)
             .worktrees()
             .iter()
-            .map(|worktree| worktree.as_ref(app).clone())
+            .map(|worktree| worktree.read(app).clone())
             .collect()
     }
 }
@@ -394,20 +394,25 @@ mod tests {
         editor, settings,
         workspace::{Workspace, WorkspaceView},
     };
-    use anyhow::Result;
     use gpui::App;
     use smol::fs;
     use tempdir::TempDir;
 
     #[test]
-    fn test_matching_paths() -> Result<()> {
-        App::test((), |mut app| async move {
-            let tmp_dir = TempDir::new("example")?;
-            fs::create_dir(tmp_dir.path().join("a")).await?;
-            fs::write(tmp_dir.path().join("a/banana"), "banana").await?;
-            fs::write(tmp_dir.path().join("a/bandana"), "bandana").await?;
-            super::init(&mut app);
-            editor::init(&mut app);
+    fn test_matching_paths() {
+        App::test_async((), |mut app| async move {
+            let tmp_dir = TempDir::new("example").unwrap();
+            fs::create_dir(tmp_dir.path().join("a")).await.unwrap();
+            fs::write(tmp_dir.path().join("a/banana"), "banana")
+                .await
+                .unwrap();
+            fs::write(tmp_dir.path().join("a/bandana"), "bandana")
+                .await
+                .unwrap();
+            app.update(|ctx| {
+                super::init(ctx);
+                editor::init(ctx);
+            });
 
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx));
@@ -420,16 +425,17 @@ mod tests {
                 "file_finder:toggle".into(),
                 (),
             );
-            let (finder, query_buffer) = workspace_view.read(&app, |view, ctx| {
-                let finder = view
+
+            let finder = app.read(|ctx| {
+                workspace_view
+                    .read(ctx)
                     .modal()
                     .cloned()
                     .unwrap()
                     .downcast::<FileFinder>()
-                    .unwrap();
-                let query_buffer = finder.as_ref(ctx).query_buffer.clone();
-                (finder, query_buffer)
+                    .unwrap()
             });
+            let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
 
             let chain = vec![finder.id(), query_buffer.id()];
             app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
@@ -452,7 +458,7 @@ mod tests {
             //     (),
             // );
             // app.finish_pending_tasks().await; // Load Buffer and open BufferView.
-            // let active_pane = workspace_view.read(&app, |view, _| view.active_pane().clone());
+            // let active_pane = workspace_view.as_ref(app).active_pane().clone();
             // assert_eq!(
             //     active_pane.state(&app),
             //     pane::State {
@@ -462,7 +468,6 @@ mod tests {
             //         }]
             //     }
             // );
-            Ok(())
-        })
+        });
     }
 }

zed/src/main.rs 🔗

@@ -1,5 +1,5 @@
 use fs::OpenOptions;
-use gpui::platform::{current as platform, PathPromptOptions, Runner as _};
+use gpui::PathPromptOptions;
 use log::LevelFilter;
 use simplelog::SimpleLogger;
 use std::{fs, path::PathBuf};
@@ -13,55 +13,48 @@ fn main() {
 
     let app = gpui::App::new(assets::Assets).unwrap();
     let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
-
-    platform::runner()
-        .set_menus(menus::MENUS)
-        .on_menu_command({
-            let app = app.clone();
-            let settings_rx = settings_rx.clone();
-            move |command| match command {
-                "app:open" => {
-                    if let Some(paths) = app.platform().prompt_for_paths(PathPromptOptions {
-                        files: true,
-                        directories: true,
-                        multiple: true,
-                    }) {
-                        app.dispatch_global_action(
-                            "workspace:open_paths",
-                            OpenParams {
-                                paths,
-                                settings: settings_rx.clone(),
-                            },
-                        );
-                    }
-                }
-                _ => app.dispatch_global_action(command, ()),
-            }
-        })
-        .on_finish_launching({
-            let mut app = app.clone();
-            move || {
-                workspace::init(&mut app);
-                editor::init(&mut app);
-                file_finder::init(&mut app);
-
-                if stdout_is_a_pty() {
-                    app.platform().activate(true);
-                }
-
-                let paths = collect_path_args();
-                if !paths.is_empty() {
-                    app.dispatch_global_action(
+    app.set_menus(menus::MENUS);
+    app.on_menu_command({
+        let settings_rx = settings_rx.clone();
+        move |command, ctx| match command {
+            "app:open" => {
+                if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions {
+                    files: true,
+                    directories: true,
+                    multiple: true,
+                }) {
+                    ctx.dispatch_global_action(
                         "workspace:open_paths",
                         OpenParams {
                             paths,
-                            settings: settings_rx,
+                            settings: settings_rx.clone(),
                         },
                     );
                 }
             }
-        })
-        .run();
+            _ => ctx.dispatch_global_action(command, ()),
+        }
+    })
+    .run(move |ctx| {
+        workspace::init(ctx);
+        editor::init(ctx);
+        file_finder::init(ctx);
+
+        if stdout_is_a_pty() {
+            ctx.platform().activate(true);
+        }
+
+        let paths = collect_path_args();
+        if !paths.is_empty() {
+            ctx.dispatch_global_action(
+                "workspace:open_paths",
+                OpenParams {
+                    paths,
+                    settings: settings_rx,
+                },
+            );
+        }
+    });
 }
 
 fn init_logger() {

zed/src/workspace/mod.rs 🔗

@@ -9,10 +9,10 @@ pub use workspace::*;
 pub use workspace_view::*;
 
 use crate::{settings::Settings, watch};
-use gpui::{App, MutableAppContext};
+use gpui::MutableAppContext;
 use std::path::PathBuf;
 
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
     app.add_global_action("workspace:open_paths", open_paths);
     app.add_global_action("app:quit", quit);
     pane::init(app);
@@ -64,10 +64,10 @@ mod tests {
 
     #[test]
     fn test_open_paths_action() {
-        App::test((), |mut app| async move {
+        App::test((), |app| {
             let settings = settings::channel(&app.font_cache()).unwrap().1;
 
-            init(&mut app);
+            init(app);
 
             let dir = temp_tree(json!({
                 "a": {
@@ -94,7 +94,7 @@ mod tests {
                     settings: settings.clone(),
                 },
             );
-            assert_eq!(app.window_ids().len(), 1);
+            assert_eq!(app.window_ids().count(), 1);
 
             app.dispatch_global_action(
                 "workspace:open_paths",
@@ -103,11 +103,19 @@ mod tests {
                     settings: settings.clone(),
                 },
             );
-            assert_eq!(app.window_ids().len(), 1);
-            let workspace_view_1 = app.root_view::<WorkspaceView>(app.window_ids()[0]).unwrap();
-            workspace_view_1.read(&app, |view, app| {
-                assert_eq!(view.workspace.as_ref(app).worktrees().len(), 2);
-            });
+            assert_eq!(app.window_ids().count(), 1);
+            let workspace_view_1 = app
+                .root_view::<WorkspaceView>(app.window_ids().next().unwrap())
+                .unwrap();
+            assert_eq!(
+                workspace_view_1
+                    .read(app)
+                    .workspace
+                    .read(app)
+                    .worktrees()
+                    .len(),
+                2
+            );
 
             app.dispatch_global_action(
                 "workspace:open_paths",
@@ -119,7 +127,7 @@ mod tests {
                     settings: settings.clone(),
                 },
             );
-            assert_eq!(app.window_ids().len(), 2);
+            assert_eq!(app.window_ids().count(), 2);
         });
     }
 }

zed/src/workspace/pane.rs 🔗

@@ -5,11 +5,11 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     keymap::Binding,
-    App, AppContext, Border, Entity, Quad, View, ViewContext,
+    AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
 };
 use std::cmp;
 
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
     app.add_action(
         "pane:activate_item",
         |pane: &mut Pane, index: &usize, ctx| {

zed/src/workspace/workspace.rs 🔗

@@ -101,7 +101,7 @@ impl Workspace {
     pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
         self.worktrees
             .iter()
-            .any(|worktree| worktree.as_ref(app).contains_path(path))
+            .any(|worktree| worktree.read(app).contains_path(path))
     }
 
     pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
@@ -112,7 +112,7 @@ impl Workspace {
 
     pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
         for tree in self.worktrees.iter() {
-            if tree.as_ref(ctx).contains_path(&path) {
+            if tree.read(ctx).contains_path(&path) {
                 return;
             }
         }
@@ -200,23 +200,22 @@ impl Entity for Workspace {
 
 #[cfg(test)]
 pub trait WorkspaceHandle {
-    fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)>;
+    fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)>;
 }
 
 #[cfg(test)]
 impl WorkspaceHandle for ModelHandle<Workspace> {
-    fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)> {
-        self.read(&app, |w, app| {
-            w.worktrees()
-                .iter()
-                .flat_map(|tree| {
-                    let tree_id = tree.id();
-                    tree.as_ref(app)
-                        .files()
-                        .map(move |file| (tree_id, file.entry_id))
-                })
-                .collect::<Vec<_>>()
-        })
+    fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)> {
+        self.read(app)
+            .worktrees()
+            .iter()
+            .flat_map(|tree| {
+                let tree_id = tree.id();
+                tree.read(app)
+                    .files()
+                    .map(move |file| (tree_id, file.entry_id))
+            })
+            .collect::<Vec<_>>()
     }
 }
 
@@ -228,8 +227,8 @@ mod tests {
     use serde_json::json;
 
     #[test]
-    fn test_open_entry() -> Result<(), Arc<anyhow::Error>> {
-        App::test((), |mut app| async move {
+    fn test_open_entry() {
+        App::test_async((), |mut app| async move {
             let dir = temp_tree(json!({
                 "a": {
                     "aa": "aa contents",
@@ -241,11 +240,9 @@ mod tests {
             app.finish_pending_tasks().await; // Open and populate worktree.
 
             // Get the first file entry.
-            let entry = workspace.read(&app, |w, app| {
-                let tree = w.worktrees.iter().next().unwrap();
-                let entry_id = tree.as_ref(app).files().next().unwrap().entry_id;
-                (tree.id(), entry_id)
-            });
+            let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
+            let entry_id = app.read(|ctx| tree.read(ctx).files().next().unwrap().entry_id);
+            let entry = (tree.id(), entry_id);
 
             // Open the same entry twice before it finishes loading.
             let (future_1, future_2) = workspace.update(&mut app, |w, app| {
@@ -255,18 +252,17 @@ mod tests {
                 )
             });
 
-            let handle_1 = future_1.await?;
-            let handle_2 = future_2.await?;
+            let handle_1 = future_1.await.unwrap();
+            let handle_2 = future_2.await.unwrap();
             assert_eq!(handle_1.id(), handle_2.id());
 
             // Open the same entry again now that it has loaded
             let handle_3 = workspace
                 .update(&mut app, |w, app| w.open_entry(entry, app).unwrap())
-                .await?;
+                .await
+                .unwrap();
 
             assert_eq!(handle_3.id(), handle_1.id());
-
-            Ok(())
         })
     }
 }

zed/src/workspace/workspace_view.rs 🔗

@@ -2,13 +2,13 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
 use crate::{settings::Settings, watch};
 use futures_core::future::LocalBoxFuture;
 use gpui::{
-    color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App,
-    AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
+    color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
+    Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
 };
 use log::{error, info};
 use std::{collections::HashSet, path::PathBuf};
 
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
     app.add_action("workspace:save", WorkspaceView::save_active_item);
     app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
     app.add_bindings(vec![
@@ -54,11 +54,11 @@ pub trait ItemViewHandle: Send + Sync {
 
 impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
     fn title(&self, app: &AppContext) -> String {
-        self.as_ref(app).title(app)
+        self.read(app).title(app)
     }
 
     fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
-        self.as_ref(app).entry_id(app)
+        self.read(app).entry_id(app)
     }
 
     fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
@@ -93,7 +93,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
     }
 
     fn is_dirty(&self, ctx: &AppContext) -> bool {
-        self.as_ref(ctx).is_dirty(ctx)
+        self.read(ctx).is_dirty(ctx)
     }
 
     fn id(&self) -> usize {
@@ -154,7 +154,7 @@ impl WorkspaceView {
     }
 
     pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool {
-        self.workspace.as_ref(app).contains_paths(paths, app)
+        self.workspace.read(app).contains_paths(paths, app)
     }
 
     pub fn open_paths(&self, paths: &[PathBuf], app: &mut MutableAppContext) {
@@ -228,8 +228,8 @@ impl WorkspaceView {
     }
 
     pub fn open_example_entry(&mut self, ctx: &mut ViewContext<Self>) {
-        if let Some(tree) = self.workspace.as_ref(ctx).worktrees().iter().next() {
-            if let Some(file) = tree.as_ref(ctx).files().next() {
+        if let Some(tree) = self.workspace.read(ctx).worktrees().iter().next() {
+            if let Some(file) = tree.read(ctx).files().next() {
                 info!("open_entry ({}, {})", tree.id(), file.entry_id);
                 self.open_entry((tree.id(), file.entry_id), ctx);
             } else {
@@ -322,7 +322,7 @@ impl WorkspaceView {
     ) -> ViewHandle<Pane> {
         let new_pane = self.add_pane(ctx);
         self.activate_pane(new_pane.clone(), ctx);
-        if let Some(item) = pane.as_ref(ctx).active_item() {
+        if let Some(item) = pane.read(ctx).active_item() {
             if let Some(clone) = item.clone_on_split(ctx.app_mut()) {
                 self.add_item(clone, ctx);
             }
@@ -389,13 +389,12 @@ impl View for WorkspaceView {
 mod tests {
     use super::{pane, Workspace, WorkspaceView};
     use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _};
-    use anyhow::Result;
     use gpui::App;
     use serde_json::json;
 
     #[test]
-    fn test_open_entry() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_open_entry() {
+        App::test_async((), |mut app| async move {
             let dir = temp_tree(json!({
                 "a": {
                     "aa": "aa contents",
@@ -407,7 +406,7 @@ mod tests {
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
             app.finish_pending_tasks().await; // Open and populate worktree.
-            let entries = workspace.file_entries(&app);
+            let entries = app.read(|ctx| workspace.file_entries(ctx));
 
             let (_, workspace_view) =
                 app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
@@ -416,19 +415,27 @@ mod tests {
             workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
             app.finish_pending_tasks().await;
 
-            workspace_view.read(&app, |w, app| {
-                assert_eq!(w.active_pane().as_ref(app).items().len(), 1);
+            app.read(|ctx| {
+                assert_eq!(
+                    workspace_view
+                        .read(ctx)
+                        .active_pane()
+                        .read(ctx)
+                        .items()
+                        .len(),
+                    1
+                )
             });
 
             // Open the second entry
             workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx));
             app.finish_pending_tasks().await;
 
-            workspace_view.read(&app, |w, app| {
-                let active_pane = w.active_pane().as_ref(app);
+            app.read(|ctx| {
+                let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
                 assert_eq!(active_pane.items().len(), 2);
                 assert_eq!(
-                    active_pane.active_item().unwrap().entry_id(app),
+                    active_pane.active_item().unwrap().entry_id(ctx),
                     Some(entries[1])
                 );
             });
@@ -437,11 +444,11 @@ mod tests {
             workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
             app.finish_pending_tasks().await;
 
-            workspace_view.read(&app, |w, app| {
-                let active_pane = w.active_pane().as_ref(app);
+            app.read(|ctx| {
+                let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
                 assert_eq!(active_pane.items().len(), 2);
                 assert_eq!(
-                    active_pane.active_item().unwrap().entry_id(app),
+                    active_pane.active_item().unwrap().entry_id(ctx),
                     Some(entries[0])
                 );
             });
@@ -453,18 +460,24 @@ mod tests {
             });
             app.finish_pending_tasks().await;
 
-            workspace_view.read(&app, |w, app| {
-                assert_eq!(w.active_pane().as_ref(app).items().len(), 3);
+            app.read(|ctx| {
+                assert_eq!(
+                    workspace_view
+                        .read(ctx)
+                        .active_pane()
+                        .read(ctx)
+                        .items()
+                        .len(),
+                    3
+                );
             });
-
-            Ok(())
-        })
+        });
     }
 
     #[test]
-    fn test_pane_actions() -> Result<()> {
-        App::test((), |mut app| async move {
-            pane::init(&mut app);
+    fn test_pane_actions() {
+        App::test_async((), |mut app| async move {
+            app.update(|ctx| pane::init(ctx));
 
             let dir = temp_tree(json!({
                 "a": {
@@ -477,7 +490,7 @@ mod tests {
             let settings = settings::channel(&app.font_cache()).unwrap().1;
             let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
             app.finish_pending_tasks().await; // Open and populate worktree.
-            let entries = workspace.file_entries(&app);
+            let entries = app.read(|ctx| workspace.file_entries(ctx));
 
             let (window_id, workspace_view) =
                 app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
@@ -485,24 +498,28 @@ mod tests {
             workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
             app.finish_pending_tasks().await;
 
-            let pane_1 = workspace_view.read(&app, |w, _| w.active_pane().clone());
+            let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
 
             app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
-            let pane_2 = workspace_view.read(&app, |w, _| w.active_pane().clone());
-            assert_ne!(pane_1, pane_2);
+            app.update(|ctx| {
+                let pane_2 = workspace_view.read(ctx).active_pane().clone();
+                assert_ne!(pane_1, pane_2);
 
-            pane_2.read(&app, |p, app| {
-                assert_eq!(p.active_item().unwrap().entry_id(app), Some(entries[0]));
-            });
+                assert_eq!(
+                    pane_2
+                        .read(ctx)
+                        .active_item()
+                        .unwrap()
+                        .entry_id(ctx.as_ref()),
+                    Some(entries[0])
+                );
 
-            app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
+                ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
 
-            workspace_view.read(&app, |w, _| {
+                let w = workspace_view.read(ctx);
                 assert_eq!(w.panes.len(), 1);
-                assert_eq!(w.active_pane(), &pane_1)
+                assert_eq!(w.active_pane(), &pane_1);
             });
-
-            Ok(())
-        })
+        });
     }
 }

zed/src/worktree/worktree.rs 🔗

@@ -409,7 +409,7 @@ pub trait WorktreeHandle {
 
 impl WorktreeHandle for ModelHandle<Worktree> {
     fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> {
-        if entry_id >= self.as_ref(app).entry_count() {
+        if entry_id >= self.read(app).entry_count() {
             return Err(anyhow!("Entry does not exist in tree"));
         }
 
@@ -461,15 +461,15 @@ pub struct FileHandle {
 
 impl FileHandle {
     pub fn path(&self, app: &AppContext) -> PathBuf {
-        self.worktree.as_ref(app).entry_path(self.entry_id).unwrap()
+        self.worktree.read(app).entry_path(self.entry_id).unwrap()
     }
 
     pub fn load_history(&self, app: &AppContext) -> impl Future<Output = Result<History>> {
-        self.worktree.as_ref(app).load_history(self.entry_id)
+        self.worktree.read(app).load_history(self.entry_id)
     }
 
     pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
-        let worktree = self.worktree.as_ref(ctx);
+        let worktree = self.worktree.read(ctx);
         worktree.save(self.entry_id, content, ctx)
     }
 
@@ -648,8 +648,8 @@ mod test {
     use std::os::unix;
 
     #[test]
-    fn test_populate_and_search() -> Result<()> {
-        App::test((), |mut app| async move {
+    fn test_populate_and_search() {
+        App::test_async((), |mut app| async move {
             let dir = temp_tree(json!({
                 "root": {
                     "apple": "",
@@ -666,12 +666,13 @@ mod test {
             }));
 
             let root_link_path = dir.path().join("root_link");
-            unix::fs::symlink(&dir.path().join("root"), &root_link_path)?;
+            unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
 
             let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx)));
             app.finish_pending_tasks().await;
 
-            tree.read(&app, |tree, _| {
+            app.read(|ctx| {
+                let tree = tree.read(ctx);
                 assert_eq!(tree.file_count(), 4);
                 let results = match_paths(&[tree.clone()], "bna", false, false, 10)
                     .iter()
@@ -685,14 +686,13 @@ mod test {
                         PathBuf::from("root_link/banana/carrot/endive"),
                     ]
                 );
-            });
-            Ok(())
-        })
+            })
+        });
     }
 
     #[test]
     fn test_save_file() {
-        App::test((), |mut app| async move {
+        App::test_async((), |mut app| async move {
             let dir = temp_tree(json!({
                 "file1": "the old contents",
             }));
@@ -700,24 +700,24 @@ mod test {
             let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx)));
             app.finish_pending_tasks().await;
 
-            let file_id = tree.read(&app, |tree, _| {
-                let entry = tree.files().next().unwrap();
+            let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
+
+            let entry = app.read(|ctx| {
+                let entry = tree.read(ctx).files().next().unwrap();
                 assert_eq!(entry.path.file_name().unwrap(), "file1");
-                entry.entry_id
+                entry
             });
-
-            let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
+            let file_id = entry.entry_id;
 
             tree.update(&mut app, |tree, ctx| {
                 smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap()
             });
 
-            let history = tree
-                .read(&app, |tree, _| tree.load_history(file_id))
+            let history = app
+                .read(|ctx| tree.read(ctx).load_history(file_id))
                 .await
                 .unwrap();
-
             assert_eq!(history.base_text, buffer.text());
-        })
+        });
     }
 }