diff --git a/Cargo.lock b/Cargo.lock index 710ad3f14f..c327163bce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,8 +399,7 @@ dependencies = [ [[package]] name = "cocoa" version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -415,8 +414,7 @@ dependencies = [ [[package]] name = "cocoa-foundation" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -445,8 +443,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "core-foundation-sys", "libc", @@ -455,14 +452,12 @@ dependencies = [ [[package]] name = "core-foundation-sys" version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" [[package]] name = "core-graphics" version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "core-foundation", @@ -474,8 +469,7 @@ dependencies = [ [[package]] name = "core-graphics-types" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "core-foundation", diff --git a/Cargo.toml b/Cargo.toml index 83f3ad985a..b60e33d042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,9 @@ members = ["zed", "gpui"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} + +# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/454 +cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} +core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"} diff --git a/gpui/examples/text.rs b/gpui/examples/text.rs index 14bc198270..df1f367cdb 100644 --- a/gpui/examples/text.rs +++ b/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; diff --git a/gpui/src/app.rs b/gpui/src/app.rs index d6996e0fee..e5f40c6ba5 100644 --- a/gpui/src/app.rs +++ b/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(&self, handle: &ModelHandle) -> &T; +pub trait ReadModel { + fn read_model(&self, handle: &ModelHandle) -> &T; } pub trait UpdateModel { @@ -55,8 +56,8 @@ pub trait UpdateModel { F: FnOnce(&mut T, &mut ModelContext) -> S; } -pub trait ViewAsRef { - fn view(&self, handle: &ViewHandle) -> &T; +pub trait ReadView { + fn read_view(&self, handle: &ViewHandle) -> &T; } pub trait UpdateView { @@ -66,27 +67,63 @@ pub trait UpdateView { F: FnOnce(&mut T, &mut ViewContext) -> S; } +pub struct Menu<'a> { + pub name: &'a str, + pub items: &'a [MenuItem<'a>], +} + +pub enum MenuItem<'a> { + Action { + name: &'a str, + keystroke: Option<&'a str>, + action: &'a str, + }, + Separator, +} + #[derive(Clone)] pub struct App(Rc>); +#[derive(Clone)] +pub struct TestAppContext(Rc>); + impl App { - pub fn test, G: FnOnce(App) -> F>( + pub fn test 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(asset_source: A, f: Fn) -> T + where + Fn: FnOnce(TestAppContext) -> F, + F: Future, + { + 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 { - 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, @@ -97,35 +134,98 @@ impl App { Ok(app) } - pub fn on_window_invalidated( - &self, - window_id: usize, - callback: F, - ) { + pub fn on_become_active(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(&self, name: S, handler: F) + pub fn on_resign_active(self, mut callback: F) -> Self where - S: Into, - V: View, - T: Any, - F: 'static + FnMut(&mut V, &T, &mut ViewContext), + 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(&self, name: S, handler: F) + pub fn on_event(self, mut callback: F) -> Self where - S: Into, - T: 'static + Any, - F: 'static + FnMut(&T, &mut MutableAppContext), + F: 'static + FnMut(Event, &mut MutableAppContext) -> bool, { - self.0.borrow_mut().add_global_action(name, handler); + 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(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(self, mut callback: F) -> Self + where + F: 'static + FnMut(Vec, &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(self, on_finish_launching: F) + where + F: 'static + FnOnce(&mut MutableAppContext), + { + 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 { + self.0.borrow().font_cache.clone() + } + + fn update 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( &self, window_id: usize, @@ -133,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, @@ -141,16 +241,6 @@ impl App { ); } - pub fn dispatch_global_action(&self, name: &str, arg: T) { - self.0 - .borrow_mut() - .dispatch_global_action(name, Box::new(arg).as_ref()); - } - - pub fn add_bindings>(&self, bindings: T) { - self.0.borrow_mut().add_bindings(bindings); - } - pub fn dispatch_keystroke( &self, window_id: usize, @@ -173,15 +263,6 @@ impl App { handle } - fn read_model(&self, handle: &ModelHandle, 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(&mut self, build_root_view: F) -> (usize, ViewHandle) where T: View, @@ -226,27 +307,21 @@ impl App { handle } - pub fn read T>(&mut self, callback: F) -> T { - callback(self.0.borrow().downgrade()) + pub fn read T>(&self, callback: F) -> T { + callback(self.0.borrow().as_ref()) } pub fn update 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(&self, handle: &ViewHandle, 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 { self.0.borrow().finish_pending_tasks() } @@ -255,12 +330,12 @@ impl App { self.0.borrow().font_cache.clone() } - pub fn platform(&self) -> Arc { + pub fn platform(&self) -> Rc { self.0.borrow().platform.clone() } } -impl UpdateModel for App { +impl UpdateModel for TestAppContext { fn update_model(&mut self, handle: &ModelHandle, update: F) -> S where T: Entity, @@ -274,7 +349,7 @@ impl UpdateModel for App { } } -impl UpdateView for App { +impl UpdateView for TestAppContext { fn update_view(&mut self, handle: &ViewHandle, update: F) -> S where T: View, @@ -295,7 +370,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext); pub struct MutableAppContext { weak_self: Option>>, - platform: Arc, + platform: Rc, font_cache: Arc, assets: Arc, ctx: AppContext, @@ -323,7 +398,7 @@ pub struct MutableAppContext { impl MutableAppContext { pub fn new( foreground: Rc, - platform: Arc, + platform: Rc, asset_source: impl AssetSource, ) -> Self { let fonts = platform.fonts(); @@ -363,8 +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 { + self.platform.clone() + } + + pub fn font_cache(&self) -> &Arc { + &self.font_cache } pub fn foreground_executor(&self) -> &Rc { @@ -487,7 +566,24 @@ impl MutableAppContext { self.ctx.render_views(window_id) } - pub fn dispatch_action( + pub fn update T>(&mut self, callback: F) -> T { + self.pending_flushes += 1; + let result = callback(); + self.flush_effects(); + result + } + + pub fn dispatch_action( + &mut self, + window_id: usize, + responder_chain: Vec, + 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], @@ -538,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(&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() { @@ -556,7 +656,7 @@ impl MutableAppContext { } } - fn add_bindings>(&mut self, bindings: T) { + pub fn add_bindings>(&mut self, bindings: T) { self.keystroke_matcher.add_bindings(bindings); } @@ -575,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!( @@ -594,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, @@ -668,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() @@ -677,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, @@ -711,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); @@ -794,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; @@ -1126,8 +1225,8 @@ impl MutableAppContext { } } -impl ModelAsRef for MutableAppContext { - fn model(&self, handle: &ModelHandle) -> &T { +impl ReadModel for MutableAppContext { + fn read_model(&self, handle: &ModelHandle) -> &T { if let Some(model) = self.ctx.models.get(&handle.model_id) { model .as_any() @@ -1164,8 +1263,8 @@ impl UpdateModel for MutableAppContext { } } -impl ViewAsRef for MutableAppContext { - fn view(&self, handle: &ViewHandle) -> &T { +impl ReadView for MutableAppContext { + fn read_view(&self, handle: &ViewHandle) -> &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") @@ -1213,6 +1312,12 @@ impl UpdateView for MutableAppContext { } } +impl AsRef for MutableAppContext { + fn as_ref(&self) -> &AppContext { + &self.ctx + } +} + pub struct AppContext { models: HashMap>, windows: HashMap, @@ -1258,8 +1363,8 @@ impl AppContext { } } -impl ModelAsRef for AppContext { - fn model(&self, handle: &ModelHandle) -> &T { +impl ReadModel for AppContext { + fn read_model(&self, handle: &ModelHandle) -> &T { if let Some(model) = self.models.get(&handle.model_id) { model .as_any() @@ -1271,8 +1376,8 @@ impl ModelAsRef for AppContext { } } -impl ViewAsRef for AppContext { - fn view(&self, handle: &ViewHandle) -> &T { +impl ReadView for AppContext { + fn read_view(&self, handle: &ViewHandle) -> &T { if let Some(window) = self.windows.get(&handle.window_id) { if let Some(view) = window.views.get(&handle.view_id) { view.as_any() @@ -1543,9 +1648,9 @@ impl<'a, T: Entity> ModelContext<'a, T> { } } -impl ModelAsRef for ModelContext<'_, M> { - fn model(&self, handle: &ModelHandle) -> &T { - self.app.model(handle) +impl ReadModel for ModelContext<'_, M> { + fn read_model(&self, handle: &ModelHandle) -> &T { + self.app.read_model(handle) } } @@ -1661,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); @@ -1687,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); @@ -1798,9 +1903,9 @@ impl<'a, T: View> ViewContext<'a, T> { } } -impl ModelAsRef for ViewContext<'_, V> { - fn model(&self, handle: &ModelHandle) -> &T { - self.app.model(handle) +impl ReadModel for ViewContext<'_, V> { + fn read_model(&self, handle: &ModelHandle) -> &T { + self.app.read_model(handle) } } @@ -1814,9 +1919,9 @@ impl UpdateModel for ViewContext<'_, V> { } } -impl ViewAsRef for ViewContext<'_, V> { - fn view(&self, handle: &ViewHandle) -> &T { - self.app.view(handle) +impl ReadView for ViewContext<'_, V> { + fn read_view(&self, handle: &ViewHandle) -> &T { + self.app.read_view(handle) } } @@ -1865,15 +1970,8 @@ impl ModelHandle { 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(&self, app: &mut A, update: F) -> S @@ -2000,15 +2098,8 @@ impl ViewHandle { 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(&self, app: &mut A, update: F) -> S @@ -2344,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()); @@ -2357,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] @@ -2394,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(); @@ -2411,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]); }) } @@ -2430,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); }); }); }); @@ -2449,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]) }) } @@ -2470,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| { @@ -2479,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| { @@ -2488,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)); }); } @@ -2503,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| { @@ -2518,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])); }) } @@ -2560,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()); }) } @@ -2624,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(); @@ -2646,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]); }) } @@ -2680,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); @@ -2697,7 +2767,7 @@ mod tests { ctx.subscribe(&observed_model, |_, _, _| {}); }); - app.update(|_| { + app.update(|| { drop(observing_view); drop(observing_model); }); @@ -2737,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) }); }); @@ -2752,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]); }) } @@ -2780,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); @@ -2795,7 +2862,7 @@ mod tests { ctx.observe(&observed_model, |_, _, _| {}); }); - app.update(|_| { + app.update(|| { drop(observing_view); drop(observing_model); }); @@ -2835,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()); @@ -2851,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(), + ], + ); }) } @@ -2887,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, _| { @@ -2896,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, _| { @@ -2904,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)); }); } @@ -2929,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| { @@ -2945,9 +3009,7 @@ mod tests { }) .await; - handle.read(&app, |view, _| { - assert_eq!(view.events, [Some(1), Some(2), Some(3), None]) - }); + app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None])) }); } @@ -2993,7 +3055,7 @@ mod tests { foo: String, } - App::test((), |mut app| async move { + App::test((), |app| { let actions = Rc::new(RefCell::new(Vec::new())); let actions_clone = actions.clone(); @@ -3067,7 +3129,7 @@ mod tests { } #[test] - fn test_dispatch_keystroke() -> Result<()> { + fn test_dispatch_keystroke() { use std::cell::Cell; #[derive(Clone)] @@ -3107,7 +3169,7 @@ mod tests { } } - App::test((), |mut app| async move { + App::test((), |app| { let mut view_1 = View::new(1); let mut view_2 = View::new(2); let mut view_3 = View::new(3); @@ -3136,12 +3198,12 @@ mod tests { app.dispatch_keystroke( window_id, vec![view_1.id(), view_2.id(), view_3.id()], - &Keystroke::parse("a")?, - )?; + &Keystroke::parse("a").unwrap(), + ) + .unwrap(); assert!(handled_action.get()); - Ok(()) - }) + }); } // #[test] @@ -3164,7 +3226,7 @@ mod tests { // } // } - // App::test(|mut app| async move { + // App::test(|app| async move { // let (window_id, _) = app.add_window(|_| View { count: 3 }); // let view_1 = app.add_view(window_id, |_| View { count: 1 }); // let view_2 = app.add_view(window_id, |_| View { count: 2 }); @@ -3191,7 +3253,7 @@ mod tests { // }); // let view_2_id = view_2.id(); - // view_1.update(&mut app, |view, ctx| { + // view_1.update(app, |view, ctx| { // view.count = 7; // ctx.notify(); // drop(view_2); @@ -3202,7 +3264,7 @@ mod tests { // assert!(invalidation.updated.contains(&view_1.id())); // assert_eq!(invalidation.removed, vec![view_2_id]); - // let view_3 = view_1.update(&mut app, |_, ctx| ctx.add_view(|_| View { count: 8 })); + // let view_3 = view_1.update(app, |_, ctx| ctx.add_view(|_| View { count: 8 })); // let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap(); // assert_eq!(invalidation.updated.len(), 1); @@ -3210,7 +3272,7 @@ mod tests { // assert!(invalidation.removed.is_empty()); // view_3 - // .update(&mut app, |_, ctx| { + // .update(app, |_, ctx| { // ctx.spawn_local(async { 9 }, |me, output, ctx| { // me.count = output; // ctx.notify(); @@ -3249,7 +3311,7 @@ mod tests { type Event = (); } - App::test((), |mut app| async move { + App::test_async((), |mut app| async move { let model = app.add_model(|_| Model); let (_, view) = app.add_window(|_| View); diff --git a/gpui/src/lib.rs b/gpui/src/lib.rs index 7b0ecdddef..b60ce9b92d 100644 --- a/gpui/src/lib.rs +++ b/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, diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs deleted file mode 100644 index 792639e9d2..0000000000 --- a/gpui/src/platform/mac/app.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::{BoolExt as _, Dispatcher, FontSystem, Window}; -use crate::{executor, platform}; -use anyhow::Result; -use cocoa::{ - appkit::{NSPasteboard, NSPasteboardTypeString}, - base::{id, nil}, - foundation::NSData, -}; -use objc::{class, msg_send, sel, sel_impl}; -use std::{ffi::c_void, rc::Rc, sync::Arc}; - -pub struct App { - dispatcher: Arc, - fonts: Arc, -} - -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 { - self.dispatcher.clone() - } - - fn activate(&self, ignoring_other_apps: bool) { - unsafe { - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()]; - } - } - - fn open_window( - &self, - options: platform::WindowOptions, - executor: Rc, - ) -> Result> { - Ok(Box::new(Window::open(options, executor, self.fonts())?)) - } - - fn fonts(&self) -> Arc { - self.fonts.clone() - } - - 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); - } - } -} diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index b7a19c3647..c2a88c12a4 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/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 { + Rc::new(MacPlatform::new()) } trait BoolExt { diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs new file mode 100644 index 0000000000..4201100a6d --- /dev/null +++ b/gpui/src/platform/mac/platform.rs @@ -0,0 +1,387 @@ +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, NSModalResponse, NSOpenPanel, NSPasteboard, + NSPasteboardTypeString, NSWindow, + }, + base::{id, nil, selector}, + foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL}, +}; +use ctor::ctor; +use objc::{ + class, + declare::ClassDecl, + msg_send, + runtime::{Class, Object, Sel}, + sel, sel_impl, +}; +use ptr::null_mut; +use std::{ + cell::RefCell, + ffi::{c_void, CStr}, + os::raw::c_char, + path::PathBuf, + ptr, + rc::Rc, + sync::Arc, +}; + +const MAC_PLATFORM_IVAR: &'static str = "platform"; +static mut APP_CLASS: *const Class = ptr::null(); +static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); + +#[ctor] +unsafe fn build_classes() { + APP_CLASS = { + let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap(); + decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); + decl.add_method( + sel!(sendEvent:), + send_event as extern "C" fn(&mut Object, Sel, id), + ); + decl.register() + }; + + APP_DELEGATE_CLASS = { + let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); + 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), + ); + decl.add_method( + sel!(applicationDidBecomeActive:), + did_become_active as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(applicationDidResignActive:), + did_resign_active as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(handleGPUIMenuItem:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(application:openFiles:), + open_files as extern "C" fn(&mut Object, Sel, id, id), + ); + decl.register() + } +} + +pub struct MacPlatform { + dispatcher: Arc, + fonts: Arc, + callbacks: RefCell, + menu_item_actions: RefCell>, +} + +#[derive(Default)] +struct Callbacks { + become_active: Option>, + resign_active: Option>, + event: Option bool>>, + menu_command: Option>, + open_files: Option)>>, + finish_launching: Option ()>>, +} + +impl MacPlatform { + pub fn new() -> Self { + Self { + dispatcher: Arc::new(Dispatcher), + fonts: Arc::new(FontSystem::new()), + callbacks: Default::default(), + menu_item_actions: Default::default(), + } + } + + unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id { + let menu_bar = NSMenu::new(nil).autorelease(); + 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(); + let menu = NSMenu::new(nil).autorelease(); + + menu.setTitle_(ns_string(menu_config.name)); + + for item_config in menu_config.items { + let item; + + match item_config { + MenuItem::Separator => { + item = NSMenuItem::separatorItem(nil); + } + MenuItem::Action { + name, + keystroke, + action, + } => { + if let Some(keystroke) = keystroke { + let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| { + panic!( + "Invalid keystroke for menu item {}:{} - {:?}", + menu_config.name, name, err + ) + }); + + let mut mask = NSEventModifierFlags::empty(); + for (modifier, flag) in &[ + (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), + (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), + (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), + ] { + if *modifier { + mask |= *flag; + } + } + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector("handleGPUIMenuItem:"), + ns_string(&keystroke.key), + ) + .autorelease(); + item.setKeyEquivalentModifierMask_(mask); + } else { + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector("handleGPUIMenuItem:"), + ns_string(""), + ) + .autorelease(); + } + + let tag = menu_item_actions.len() as NSInteger; + let _: () = msg_send![item, setTag: tag]; + menu_item_actions.push(action.to_string()); + } + } + + menu.addItem_(item); + } + + menu_bar_item.setSubmenu_(menu); + menu_bar.addItem_(menu_bar_item); + } + + menu_bar + } +} + +impl platform::Platform for MacPlatform { + fn on_become_active(&self, callback: Box) { + self.callbacks.borrow_mut().become_active = Some(callback); + } + + fn on_resign_active(&self, callback: Box) { + self.callbacks.borrow_mut().resign_active = Some(callback); + } + + fn on_event(&self, callback: Box bool>) { + self.callbacks.borrow_mut().event = Some(callback); + } + + fn on_menu_command(&self, callback: Box) { + self.callbacks.borrow_mut().menu_command = Some(callback); + } + + fn on_open_files(&self, callback: Box)>) { + self.callbacks.borrow_mut().open_files = Some(callback); + } + + fn run(&self, on_finish_launching: Box ()>) { + self.callbacks.borrow_mut().finish_launching = Some(on_finish_launching); + + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + 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::()); + (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); + } + } + + fn dispatcher(&self) -> Arc { + 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, + ) -> Result> { + Ok(Box::new(Window::open(options, executor, self.fonts())?)) + } + + fn prompt_for_paths( + &self, + options: platform::PathPromptOptions, + ) -> Option> { + 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 { + 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); + } + } + + 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_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) { + 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; + } + } + } + + msg_send![super(this, class!(NSApplication)), sendEvent: native_event] + } +} + +extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + + 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 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 platform = unsafe { get_platform(this) }; + if let Some(callback) = platform.callbacks.borrow_mut().resign_active.as_mut() { + callback(); + } +} + +extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { + let paths = unsafe { + (0..paths.count()) + .into_iter() + .filter_map(|i| { + let path = paths.objectAtIndex(i); + match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() { + Ok(string) => Some(PathBuf::from(string)), + Err(err) => { + log::error!("error converting path to string: {}", err); + None + } + } + }) + .collect::>() + }; + 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 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) = platform.menu_item_actions.borrow().get(index) { + callback(&action); + } + } + } +} + +unsafe fn ns_string(string: &str) -> id { + NSString::alloc(nil).init_str(string).autorelease() +} diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs deleted file mode 100644 index c407bf59d7..0000000000 --- a/gpui/src/platform/mac/runner.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::platform::Event; -use cocoa::{ - appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - base::{id, nil}, - foundation::{NSArray, NSAutoreleasePool, NSString}, -}; -use ctor::ctor; -use objc::{ - class, - declare::ClassDecl, - msg_send, - runtime::{Class, Object, Sel}, - sel, sel_impl, -}; -use std::{ - ffi::CStr, - os::raw::{c_char, c_void}, - path::PathBuf, - ptr, -}; - -const RUNNER_IVAR: &'static str = "runner"; -static mut APP_CLASS: *const Class = ptr::null(); -static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); - -#[ctor] -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_method( - sel!(sendEvent:), - send_event as extern "C" fn(&mut Object, Sel, id), - ); - decl.register() - }; - - APP_DELEGATE_CLASS = { - let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); - decl.add_ivar::<*mut c_void>(RUNNER_IVAR); - decl.add_method( - sel!(applicationDidFinishLaunching:), - did_finish_launching as extern "C" fn(&mut Object, Sel, id), - ); - decl.add_method( - sel!(applicationDidBecomeActive:), - did_become_active as extern "C" fn(&mut Object, Sel, id), - ); - decl.add_method( - sel!(applicationDidResignActive:), - did_resign_active as extern "C" fn(&mut Object, Sel, id), - ); - decl.add_method( - sel!(application:openFiles:), - open_files as extern "C" fn(&mut Object, Sel, id, id), - ); - decl.register() - } -} - -#[derive(Default)] -pub struct Runner { - finish_launching_callback: Option>, - become_active_callback: Option>, - resign_active_callback: Option>, - event_callback: Option bool>>, - open_files_callback: Option)>>, -} - -impl Runner { - pub fn new() -> Self { - Default::default() - } -} - -impl crate::platform::Runner for Runner { - fn on_finish_launching(mut self, callback: F) -> Self { - self.finish_launching_callback = Some(Box::new(callback)); - self - } - - fn on_become_active(mut self, callback: F) -> Self { - log::info!("become active"); - self.become_active_callback = Some(Box::new(callback)); - self - } - - fn on_resign_active(mut self, callback: F) -> Self { - self.resign_active_callback = Some(Box::new(callback)); - self - } - - fn on_event bool>(mut self, callback: F) -> Self { - self.event_callback = Some(Box::new(callback)); - self - } - - fn on_open_files)>(mut self, callback: F) -> Self { - self.open_files_callback = Some(Box::new(callback)); - self - } - - fn run(self) { - unsafe { - let self_ptr = Box::into_raw(Box::new(self)); - - let pool = NSAutoreleasePool::new(nil); - let app: id = msg_send![APP_CLASS, sharedApplication]; - let _: () = msg_send![ - app, - setActivationPolicy: NSApplicationActivationPolicyRegular - ]; - (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - let _: () = msg_send![app, setDelegate: app_delegate]; - let _: () = msg_send![app, run]; - let _: () = msg_send![pool, drain]; - // The Runner is done running when we get here, so we can reinstantiate the Box and drop it. - Box::from_raw(self_ptr); - } - } -} - -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) -} - -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 { - let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event]; - } -} - -extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { - let runner = unsafe { get_runner(this) }; - if let Some(callback) = runner.finish_launching_callback.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() { - 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() { - callback(); - } -} - -extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { - let paths = unsafe { - (0..paths.count()) - .into_iter() - .filter_map(|i| { - let path = paths.objectAtIndex(i); - match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() { - Ok(string) => Some(PathBuf::from(string)), - Err(err) => { - log::error!("error converting path to string: {}", err); - None - } - } - }) - .collect::>() - }; - let runner = unsafe { get_runner(this) }; - if let Some(callback) = runner.open_files_callback.as_mut() { - callback(paths); - } -} diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index ce4c27f923..39825c941e 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -15,32 +15,34 @@ use crate::{ vector::Vector2F, }, text_layout::Line, - Scene, + Menu, Scene, }; use anyhow::Result; 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(self, callback: F) -> Self where; - fn on_become_active(self, callback: F) -> Self; - fn on_resign_active(self, callback: F) -> Self; - fn on_event bool>(self, callback: F) -> Self; - fn on_open_files)>(self, callback: F) -> Self; - fn run(self); -} +pub trait Platform { + fn on_menu_command(&self, callback: Box); + fn on_become_active(&self, callback: Box); + fn on_resign_active(&self, callback: Box); + fn on_event(&self, callback: Box bool>); + fn on_open_files(&self, callback: Box)>); + fn run(&self, on_finish_launching: Box ()>); -pub trait App { fn dispatcher(&self) -> Arc; + fn fonts(&self) -> Arc; + fn activate(&self, ignoring_other_apps: bool); fn open_window( &self, options: WindowOptions, executor: Rc, ) -> Result>; - fn fonts(&self) -> Arc; + fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; + fn quit(&self); fn copy(&self, text: &str); + fn set_menus(&self, menus: &[Menu]); } pub trait Dispatcher: Send + Sync { @@ -64,6 +66,12 @@ pub struct WindowOptions<'a> { pub title: Option<&'a str>, } +pub struct PathPromptOptions { + pub files: bool, + pub directories: bool, + pub multiple: bool, +} + pub trait FontSystem: Send + Sync { fn load_family(&self, name: &str) -> anyhow::Result>; fn select_font( diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index c42b94628a..f1d6bead66 100644 --- a/gpui/src/platform/test.rs +++ b/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, fonts: Arc, } @@ -17,9 +17,7 @@ pub struct Window { resize_handlers: Vec>, } -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) {} + + fn on_become_active(&self, _: Box) {} + + fn on_resign_active(&self, _: Box) {} + + fn on_event(&self, _: Box bool>) {} + + fn on_open_files(&self, _: Box)>) {} + + fn run(&self, _on_finish_launching: Box ()>) { + unimplemented!() + } + fn dispatcher(&self) -> Arc { self.dispatcher.clone() } + fn fonts(&self) -> std::sync::Arc { + self.fonts.clone() + } + fn activate(&self, _ignoring_other_apps: bool) {} fn open_window( @@ -43,8 +59,12 @@ impl super::App for App { Ok(Box::new(Window::new(options.bounds.size()))) } - fn fonts(&self) -> std::sync::Arc { - self.fonts.clone() + fn set_menus(&self, _menus: &[crate::Menu]) {} + + fn quit(&self) {} + + fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option> { + None } fn copy(&self, _: &str) {} @@ -96,6 +116,6 @@ impl super::Window for Window { } } -pub fn app() -> impl super::App { - App::new() +pub fn platform() -> impl super::Platform { + Platform::new() } diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index 7c3be96a72..6efa214995 100644 --- a/gpui/src/presenter.rs +++ b/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(); diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 3ce9ba96ca..def4d13757 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2315,13 +2315,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()) @@ -2333,7 +2333,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(); }); @@ -2837,12 +2837,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()) @@ -2855,7 +2855,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!( @@ -2874,7 +2874,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(); @@ -2884,7 +2884,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!( @@ -2910,7 +2910,7 @@ mod tests { assert!(buffer.is_dirty()); }); - model.update(&mut app, |_, _| { + model.update(app, |_, _| { assert_eq!( *events.borrow(), &[Event::Edited(vec![Edit { diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs index e00c11f1fd..eabd891791 100644 --- a/zed/src/editor/buffer_element.rs +++ b/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); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 5960015b45..f7c516fb60 100644 --- a/zed/src/editor/buffer_view.rs +++ b/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; @@ -23,7 +24,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")), @@ -195,7 +196,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(app) @@ -245,7 +246,7 @@ impl BufferView { layouts: &[Arc], 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; @@ -294,7 +295,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(); @@ -319,8 +320,8 @@ impl BufferView { scroll_position: Vector2F, ctx: &mut ViewContext, ) { - 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); @@ -354,7 +355,7 @@ impl BufferView { where T: IntoIterator>, { - let map = self.display_map.as_ref(ctx); + let map = self.display_map.read(ctx); let mut selections = Vec::new(); for range in ranges { selections.push(Selection { @@ -371,7 +372,7 @@ impl BufferView { fn insert(&mut self, text: &String, ctx: &mut ViewContext) { let mut offset_ranges = SmallVec::<[Range; 32]>::new(); { - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); for selection in self.selections(ctx.app()) { let start = selection.start.to_offset(buffer).unwrap(); let end = selection.end.to_offset(buffer).unwrap(); @@ -424,8 +425,8 @@ impl BufferView { self.start_transaction(ctx); let mut selections = self.selections(ctx.app()).to_vec(); { - 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 selections { if selection.range(buffer).is_empty() { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); @@ -459,10 +460,10 @@ impl BufferView { } pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext) { - let mut selections = self.selections(ctx.app()).to_vec(); + let app = ctx.app(); + let mut selections = self.selections(app).to_vec(); { - let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -487,8 +488,8 @@ impl BufferView { pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext) { let mut selections = self.selections(ctx.app()).to_vec(); { - 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 selections { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); let cursor = map @@ -510,7 +511,7 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -536,8 +537,8 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { 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 selections { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); let cursor = map @@ -558,7 +559,7 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -587,8 +588,8 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { 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 selections { let head = selection.head().to_display_point(map, app).unwrap(); let (head, goal_column) = @@ -609,7 +610,7 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -638,8 +639,8 @@ impl BufferView { let mut selections = self.selections(ctx.app()).to_vec(); { 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 selections { let head = selection.head().to_display_point(map, app).unwrap(); let (head, goal_column) = @@ -663,14 +664,14 @@ impl BufferView { self.selections(app) .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 { self.selections(app) .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>( @@ -678,7 +679,7 @@ impl BufferView { range: Range, app: &'a AppContext, ) -> impl 'a + Iterator> { - 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); @@ -698,7 +699,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); let selections = self.selections(app); match selections.binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap()) { Ok(index) => index, @@ -716,14 +717,14 @@ impl BufferView { fn selections<'a>(&self, app: &'a AppContext) -> &'a [Selection] { self.buffer - .as_ref(app) + .read(app) .selections(self.selection_set_id) .unwrap() } fn update_selections(&self, mut selections: Vec, ctx: &mut ViewContext) { // Merge overlapping selections. - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let mut i = 1; while i < selections.len() { if selections[i - 1] @@ -781,7 +782,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(app) { let (start, end) = selection.display_range(map, app).sorted(); let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row; @@ -811,8 +812,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(app) .iter() @@ -857,7 +858,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 == ' ' { @@ -871,7 +872,7 @@ impl BufferView { } fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result> { - 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)?; @@ -892,7 +893,7 @@ impl BufferView { pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext) { self.display_map.update(ctx, |map, ctx| { - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let ranges = self .selections(ctx.app()) .iter() @@ -903,23 +904,23 @@ impl BufferView { } pub fn line(&self, display_row: u32, app: &AppContext) -> Result { - 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 { - 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 { @@ -963,7 +964,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; @@ -984,7 +985,7 @@ impl BufferView { layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result>> { - 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; @@ -1020,7 +1021,7 @@ impl BufferView { layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result>> { - 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 { @@ -1203,7 +1204,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() @@ -1214,7 +1215,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) -> Option @@ -1231,7 +1232,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() } } @@ -1240,112 +1241,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::>(); - 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::>(); + 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::>(); - 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::>(); + 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::>(); - 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::>(); + 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::>(); - 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::>(); + 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::>(); - 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::>(); + 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::>(); - 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::>(); + 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))); @@ -1353,19 +1367,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, @@ -1393,8 +1405,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()), @@ -1448,24 +1461,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), @@ -1476,7 +1485,7 @@ mod tests { ) })?; - view.update(&mut app, |view, ctx| { + view.update(app, |view, ctx| { view.move_down(&(), ctx); assert_eq!( view.selection_ranges(ctx.app()), @@ -1495,8 +1504,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") }); @@ -1504,7 +1513,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 @@ -1515,17 +1524,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" + ); }) } diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index f5d76636ff..58f51cee94 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -22,7 +22,7 @@ pub struct FoldMap { impl FoldMap { pub fn new(buffer: ModelHandle, 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>, 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)); + let mut map = FoldMap::new(buffer.clone(), app.as_ref()); - 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>(()) - })?; + 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(&mut app, |buffer, ctx| { + 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::>()) - })?; + 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::>() + }); - app.read(|app| { - map.apply_edits(&edits, app)?; - assert_eq!(map.text(app), "123a…c123c…eeeee"); - Ok::<(), anyhow::Error>(()) - })?; + map.apply_edits(&edits, app.as_ref()).unwrap(); + assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee"); - let edits = buffer.update(&mut app, |buffer, ctx| { + 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))?; - Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::>()) - })?; + buffer + .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx)) + .unwrap(); + buffer.edits_since(start_version).collect::>() + }); - 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…c123456eee"); - map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?; - assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee"); + 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"); + }); + } - Ok(()) - }) + #[test] + fn test_overlapping_folds() { + App::test((), |app| { + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); + 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_overlapping_folds() -> 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))); - 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()); - #[test] - fn test_merging_folds_via_edit() -> Result<()> { - App::test((), |mut app| async move { - let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); - let mut map = app.read(|app| 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.as_ref(), + ) + .unwrap(); + assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee"); - 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 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::>()) - })?; + buffer + .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx)) + .unwrap(); + buffer.edits_since(start_version).collect::>() + }); - app.read(|app| { - map.apply_edits(&edits, app)?; - assert_eq!(map.text(app), "aa…eeeee"); - Ok(()) - }) - }) + 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::(); 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> = 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::>()) - })?; + buffer.edits_since(start_version).collect::>() + }); - app.read(|app| { - map.apply_edits(&edits, app)?; + map.apply_edits(&edits, app.as_ref()).unwrap(); - 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; + 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; - expected_text.replace_range(fold_range.start..fold_range.end, "…"); - } - expected_buffer_rows.extend((0..=next_row).rev()); - expected_buffer_rows.reverse(); + expected_text.replace_range(fold_range.start..fold_range.end, "…"); + } + expected_buffer_rows.extend((0..=next_row).rev()); + expected_buffer_rows.reverse(); - assert_eq!(map.text(app), expected_text); + assert_eq!(map.text(app.as_ref()), expected_text); - 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::>(), - expected_buffer_rows[idx..], - ); - } - - Ok::<(), anyhow::Error>(()) - })?; - - 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::>(), + 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); + 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, - )?; + 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), "aa…cccc\nd…eeeee\nffffff\n"); - assert_eq!(map.buffer_rows(0)?.collect::>(), vec![0, 3, 5, 6]); - assert_eq!(map.buffer_rows(3)?.collect::>(), vec![6]); - - Ok(()) - }) - }) + assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n"); + assert_eq!( + map.buffer_rows(0).unwrap().collect::>(), + vec![0, 3, 5, 6] + ); + assert_eq!(map.buffer_rows(3).unwrap().collect::>(), vec![6]); + }); } impl FoldMap { @@ -713,7 +694,7 @@ mod tests { } fn merged_fold_ranges(&self, app: &AppContext) -> Vec> { - let buffer = self.buffer.as_ref(app); + let buffer = self.buffer.read(app); let mut fold_ranges = self .folds .iter() diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 2679766aa1..e44193104e 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -108,7 +108,7 @@ impl DisplayMap { app: &AppContext, ) -> Result { 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 { 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 { - 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), - ) - })?; + 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(); - map.read(&app, |map, ctx| { - assert_eq!( - map.chars_at(DisplayPoint::new(1, 0), ctx)? - .take(10) - .collect::(), - " b bb" - ); - assert_eq!( - map.chars_at(DisplayPoint::new(1, 2), ctx)? - .take(10) - .collect::(), - " b bbbb" - ); - assert_eq!( - map.chars_at(DisplayPoint::new(1, 6), ctx)? - .take(13) - .collect::(), - " bbbbb\nc c" - ); - - Ok::<(), Error>(()) - })?; - - Ok(()) - }) + let map = map.read(app); + assert_eq!( + map.chars_at(DisplayPoint::new(1, 0), app.as_ref()) + .unwrap() + .take(10) + .collect::(), + " b bb" + ); + assert_eq!( + map.chars_at(DisplayPoint::new(1, 2), app.as_ref()) + .unwrap() + .take(10) + .collect::(), + " b bbbb" + ); + assert_eq!( + map.chars_at(DisplayPoint::new(1, 6), app.as_ref()) + .unwrap() + .take(13) + .collect::(), + " 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) + ) + }); } } diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index c951d12934..169d2243a4 100644 --- a/zed/src/file_finder.rs +++ b/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, ctx: &mut ViewContext) { - 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 { 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::() - .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(()) - }) + }); } } diff --git a/zed/src/lib.rs b/zed/src/lib.rs index a66a892ebf..752df470c5 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -1,6 +1,7 @@ pub mod assets; pub mod editor; pub mod file_finder; +pub mod menus; mod operation_queue; pub mod settings; mod sum_tree; diff --git a/zed/src/main.rs b/zed/src/main.rs index ed52fb6163..407b4952c1 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,10 +1,10 @@ use fs::OpenOptions; -use gpui::platform::{current as platform, Runner as _}; +use gpui::PathPromptOptions; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; use zed::{ - assets, editor, file_finder, settings, + assets, editor, file_finder, menus, settings, workspace::{self, OpenParams}, }; @@ -13,32 +13,48 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); - - { - let mut app = app.clone(); - platform::runner() - .on_finish_launching(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() { diff --git a/zed/src/menus.rs b/zed/src/menus.rs new file mode 100644 index 0000000000..cda749c30e --- /dev/null +++ b/zed/src/menus.rs @@ -0,0 +1,60 @@ +use gpui::{Menu, MenuItem}; + +#[cfg(target_os = "macos")] +pub const MENUS: &'static [Menu] = &[ + Menu { + name: "Zed", + items: &[ + MenuItem::Action { + name: "About Zed…", + keystroke: None, + action: "app:about-zed", + }, + MenuItem::Separator, + MenuItem::Action { + name: "Quit", + keystroke: Some("cmd-q"), + action: "app:quit", + }, + ], + }, + Menu { + name: "File", + items: &[MenuItem::Action { + name: "Open…", + keystroke: Some("cmd-o"), + action: "app:open", + }], + }, + Menu { + name: "Edit", + items: &[ + MenuItem::Action { + name: "Undo", + keystroke: Some("cmd-z"), + action: "editor:undo", + }, + MenuItem::Action { + name: "Redo", + keystroke: Some("cmd-Z"), + action: "editor:redo", + }, + MenuItem::Separator, + MenuItem::Action { + name: "Cut", + keystroke: Some("cmd-x"), + action: "editor:cut", + }, + MenuItem::Action { + name: "Copy", + keystroke: Some("cmd-c"), + action: "editor:copy", + }, + MenuItem::Action { + name: "Paste", + keystroke: Some("cmd-v"), + action: "editor:paste", + }, + ], + }, +]; diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index c58fa864d2..a441eb8b8b 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -9,11 +9,12 @@ 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); workspace_view::init(app); } @@ -50,6 +51,10 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { app.add_window(|ctx| WorkspaceView::new(workspace, params.settings.clone(), ctx)); } +fn quit(_: &(), app: &mut MutableAppContext) { + app.platform().quit(); +} + #[cfg(test)] mod tests { use super::*; @@ -59,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": { @@ -89,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", @@ -98,11 +103,19 @@ mod tests { settings: settings.clone(), }, ); - assert_eq!(app.window_ids().len(), 1); - let workspace_view_1 = app.root_view::(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::(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", @@ -114,7 +127,7 @@ mod tests { settings: settings.clone(), }, ); - assert_eq!(app.window_ids().len(), 2); + assert_eq!(app.window_ids().count(), 2); }); } } diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index fad299aa8b..58acb86abb 100644 --- a/zed/src/workspace/pane.rs +++ b/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| { diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 092144791c..b4e5a85e58 100644 --- a/zed/src/workspace/workspace.rs +++ b/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) { @@ -112,7 +112,7 @@ impl Workspace { pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext) { 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 { - 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::>() - }) + 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::>() } } @@ -228,8 +227,8 @@ mod tests { use serde_json::json; #[test] - fn test_open_entry() -> Result<(), Arc> { - 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(()) }) } } diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 5810245be4..6a9b3ca320 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/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 ItemViewHandle for ViewHandle { 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 { @@ -93,7 +93,7 @@ impl ItemViewHandle for ViewHandle { } 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) { - 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 { 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(()) - }) + }); } } diff --git a/zed/src/worktree/worktree.rs b/zed/src/worktree/worktree.rs index 3072ce2904..2e685261ce 100644 --- a/zed/src/worktree/worktree.rs +++ b/zed/src/worktree/worktree.rs @@ -409,7 +409,7 @@ pub trait WorktreeHandle { impl WorktreeHandle for ModelHandle { fn file(&self, entry_id: usize, app: &AppContext) -> Result { - 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> { - 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> { - 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(); - assert_eq!(entry.path.file_name().unwrap(), "file1"); - entry.entry_id - }); - 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 + }); + 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.as_ref(), buffer.text()); - }) + }); } }