From 7e5186e4a0d78c4fa07c3b3b8ad8218019b32ac5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 7 Apr 2021 17:48:22 -0700 Subject: [PATCH 01/15] Start work on a native application menu Add an application menu with a quit command, bound to command-q --- gpui/src/platform/mac/app.rs | 7 ++--- gpui/src/platform/mac/runner.rs | 53 ++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index daf8335f60..b3b2a788cf 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -1,8 +1,7 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; -use cocoa::base::id; -use objc::{class, msg_send, sel, sel_impl}; +use cocoa::{appkit::NSApplication, base::nil}; use std::{rc::Rc, sync::Arc}; pub struct App { @@ -26,8 +25,8 @@ impl platform::App for App { 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()]; + let app = NSApplication::sharedApplication(nil); + app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc()); } } diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index c407bf59d7..4b0ebe7dfd 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -1,7 +1,10 @@ use crate::platform::Event; use cocoa::{ - appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - base::{id, nil}, + appkit::{ + NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSMenu, + NSMenuItem, NSWindow, + }, + base::{id, nil, selector}, foundation::{NSArray, NSAutoreleasePool, NSString}, }; use ctor::ctor; @@ -106,16 +109,16 @@ impl crate::platform::Runner for Runner { 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.setActivationPolicy_(NSApplicationActivationPolicyRegular); + (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*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]; + app.setMainMenu_(create_menu_bar()); + app.setDelegate_(app_delegate); + app.run(); + 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); } @@ -186,3 +189,33 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { callback(paths); } } + +unsafe fn create_menu_bar() -> id { + let menu_bar = NSMenu::new(nil).autorelease(); + + // App menu + let app_menu_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Application"), + Sel::from_ptr(ptr::null()), + ns_string(""), + ) + .autorelease(); + let quit_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Quit"), + selector("terminate:"), + ns_string("q\0"), + ) + .autorelease(); + let app_menu = NSMenu::new(nil).autorelease(); + app_menu.addItem_(quit_item); + app_menu_item.setSubmenu_(app_menu); + menu_bar.addItem_(app_menu_item); + + menu_bar +} + +unsafe fn ns_string(string: &str) -> id { + NSString::alloc(nil).init_str(string).autorelease() +} From 0a1277468007aca3a0e362ad8500491919bd9745 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 7 Apr 2021 17:49:44 -0700 Subject: [PATCH 02/15] Add a stub of a native 'File' menu --- gpui/src/platform/mac/runner.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index 4b0ebe7dfd..a4b7281753 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -213,6 +213,27 @@ unsafe fn create_menu_bar() -> id { app_menu_item.setSubmenu_(app_menu); menu_bar.addItem_(app_menu_item); + // File menu + let file_menu_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("File"), + Sel::from_ptr(ptr::null()), + ns_string(""), + ) + .autorelease(); + let open_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string("Open"), + selector("openDocument:"), + ns_string("o\0"), + ) + .autorelease(); + let file_menu = NSMenu::new(nil).autorelease(); + file_menu.setTitle_(ns_string("File")); + file_menu.addItem_(open_item); + file_menu_item.setSubmenu_(file_menu); + menu_bar.addItem_(file_menu_item); + menu_bar } From 334de06322f4a4c79bc1d9bbf7908eb9a080766d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 15:56:21 -0700 Subject: [PATCH 03/15] Create an API for assigning the menubar contents --- gpui/src/app.rs | 18 ++++ gpui/src/platform/mac/app.rs | 8 ++ gpui/src/platform/mac/runner.rs | 159 ++++++++++++++++++++++---------- gpui/src/platform/mod.rs | 7 +- gpui/src/platform/test.rs | 2 + zed/src/lib.rs | 1 + zed/src/main.rs | 24 +++-- zed/src/menus.rs | 52 +++++++++++ zed/src/workspace/mod.rs | 5 + 9 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 zed/src/menus.rs diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 8dbc34b893..8863bb05eb 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -66,6 +66,20 @@ 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>); @@ -365,6 +379,10 @@ impl MutableAppContext { &self.ctx } + pub fn platform(&self) -> Arc { + self.platform.clone() + } + pub fn foreground_executor(&self) -> &Rc { &self.foreground } diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index b3b2a788cf..b77cb2ee51 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -2,6 +2,7 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; use cocoa::{appkit::NSApplication, base::nil}; +use objc::{msg_send, sel, sel_impl}; use std::{rc::Rc, sync::Arc}; pub struct App { @@ -41,4 +42,11 @@ impl platform::App for App { fn fonts(&self) -> Arc { self.fonts.clone() } + + fn quit(&self) { + unsafe { + let app = NSApplication::sharedApplication(nil); + let _: () = msg_send![app, terminate: nil]; + } + } } diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index a4b7281753..1783e03ea0 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -1,11 +1,11 @@ -use crate::platform::Event; +use crate::{keymap::Keystroke, platform::Event, Menu, MenuItem}; use cocoa::{ appkit::{ - NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSMenu, - NSMenuItem, NSWindow, + NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, + NSEventModifierFlags, NSMenu, NSMenuItem, NSWindow, }, base::{id, nil, selector}, - foundation::{NSArray, NSAutoreleasePool, NSString}, + foundation::{NSArray, NSAutoreleasePool, NSInteger, NSString}, }; use ctor::ctor; use objc::{ @@ -53,6 +53,10 @@ unsafe fn build_classes() { 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), @@ -68,12 +72,89 @@ pub struct Runner { resign_active_callback: Option>, event_callback: Option bool>>, open_files_callback: Option)>>, + menu_command_callback: Option>, + menu_item_actions: Vec, } impl Runner { pub fn new() -> Self { Default::default() } + + unsafe fn create_menu_bar(&mut self, menus: &[Menu]) -> id { + let menu_bar = NSMenu::new(nil).autorelease(); + self.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 = self.menu_item_actions.len() as NSInteger; + let _: () = msg_send![item, setTag: tag]; + self.menu_item_actions.push(action.to_string()); + } + } + + menu.addItem_(item); + } + + menu_bar_item.setSubmenu_(menu); + menu_bar.addItem_(menu_bar_item); + } + + menu_bar + } } impl crate::platform::Runner for Runner { @@ -82,6 +163,11 @@ impl crate::platform::Runner for Runner { self } + fn on_menu_command(mut self, callback: F) -> Self { + self.menu_command_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)); @@ -103,6 +189,14 @@ impl crate::platform::Runner for Runner { self } + fn set_menus(mut self, menus: &[Menu]) -> Self { + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setMainMenu_(self.create_menu_bar(menus)); + } + self + } + fn run(self) { unsafe { let self_ptr = Box::into_raw(Box::new(self)); @@ -114,7 +208,6 @@ impl crate::platform::Runner for Runner { app.setActivationPolicy_(NSApplicationActivationPolicyRegular); (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - app.setMainMenu_(create_menu_bar()); app.setDelegate_(app_delegate); app.run(); pool.drain(); @@ -190,51 +283,17 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { } } -unsafe fn create_menu_bar() -> id { - let menu_bar = NSMenu::new(nil).autorelease(); - - // App menu - let app_menu_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Application"), - Sel::from_ptr(ptr::null()), - ns_string(""), - ) - .autorelease(); - let quit_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Quit"), - selector("terminate:"), - ns_string("q\0"), - ) - .autorelease(); - let app_menu = NSMenu::new(nil).autorelease(); - app_menu.addItem_(quit_item); - app_menu_item.setSubmenu_(app_menu); - menu_bar.addItem_(app_menu_item); - - // File menu - let file_menu_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("File"), - Sel::from_ptr(ptr::null()), - ns_string(""), - ) - .autorelease(); - let open_item = NSMenuItem::alloc(nil) - .initWithTitle_action_keyEquivalent_( - ns_string("Open"), - selector("openDocument:"), - ns_string("o\0"), - ) - .autorelease(); - let file_menu = NSMenu::new(nil).autorelease(); - file_menu.setTitle_(ns_string("File")); - file_menu.addItem_(open_item); - file_menu_item.setSubmenu_(file_menu); - menu_bar.addItem_(file_menu_item); - - menu_bar +extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { + unsafe { + let runner = get_runner(this); + if let Some(callback) = runner.menu_command_callback.as_mut() { + let tag: NSInteger = msg_send![item, tag]; + let index = tag as usize; + if let Some(action) = runner.menu_item_actions.get(index) { + callback(&action); + } + } + } } unsafe fn ns_string(string: &str) -> id { diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index 5bd7e4659e..bbd2dc9838 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -15,7 +15,7 @@ use crate::{ vector::Vector2F, }, text_layout::Line, - Scene, + Menu, Scene, }; use anyhow::Result; use async_task::Runnable; @@ -23,11 +23,13 @@ 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_finish_launching(self, callback: F) -> Self; + fn on_menu_command(self, callback: F) -> Self; 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 set_menus(self, menus: &[Menu]) -> Self; fn run(self); } @@ -40,6 +42,7 @@ pub trait App { executor: Rc, ) -> Result>; fn fonts(&self) -> Arc; + fn quit(&self); } pub trait Dispatcher: Send + Sync { diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 545ab10e34..067184adea 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -46,6 +46,8 @@ impl super::App for App { fn fonts(&self) -> std::sync::Arc { self.fonts.clone() } + + fn quit(&self) {} } impl Window { 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..08fb098784 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -4,7 +4,7 @@ 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}, }; @@ -14,10 +14,18 @@ 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 || { + platform::runner() + .set_menus(menus::MENUS) + .on_menu_command({ + let app = app.clone(); + move |command| { + log::info!("menu command: {}", command); + app.dispatch_global_action(command, ()) + } + }) + .on_finish_launching({ + let mut app = app.clone(); + move || { workspace::init(&mut app); editor::init(&mut app); file_finder::init(&mut app); @@ -36,9 +44,9 @@ fn main() { }, ); } - }) - .run(); - } + } + }) + .run(); } fn init_logger() { diff --git a/zed/src/menus.rs b/zed/src/menus.rs new file mode 100644 index 0000000000..c6ef18b746 --- /dev/null +++ b/zed/src/menus.rs @@ -0,0 +1,52 @@ +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: "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..b7a76f9445 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -14,6 +14,7 @@ use std::path::PathBuf; pub fn init(app: &mut App) { 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::*; From f656b387b3fa6e63628ff8c2cd5d88a63efda899 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 16:11:45 -0700 Subject: [PATCH 04/15] Call SetActivationPolicy at the proper time If this method is called too early, the menu bar won't be clickable on startup until the window loses focus. Calling it once the application finishes launching seems to fix the issue. See https://github.com/glfw/glfw/issues/1648 --- gpui/src/platform/mac/runner.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs index 1783e03ea0..2c2c3ecab4 100644 --- a/gpui/src/platform/mac/runner.rs +++ b/gpui/src/platform/mac/runner.rs @@ -205,7 +205,6 @@ impl crate::platform::Runner for Runner { let app: id = msg_send![APP_CLASS, sharedApplication]; let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - app.setActivationPolicy_(NSApplicationActivationPolicyRegular); (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); app.setDelegate_(app_delegate); @@ -241,9 +240,14 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { } 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(); + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + + let runner = get_runner(this); + if let Some(callback) = runner.finish_launching_callback.take() { + callback(); + } } } From 7ebcbdc0cb5b7f8014f8d68ec9aed2d184b2dcf2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 8 Apr 2021 22:25:54 -0700 Subject: [PATCH 05/15] Implement File > Open menu item --- Cargo.lock | 18 ++++++----------- Cargo.toml | 6 ++++++ gpui/src/platform/mac/app.rs | 39 ++++++++++++++++++++++++++++++++++-- gpui/src/platform/mod.rs | 7 +++++++ gpui/src/platform/test.rs | 4 ++++ zed/src/main.rs | 23 +++++++++++++++++---- zed/src/menus.rs | 10 ++++++++- 7 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38d75ed246..ce7dedfdf6 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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" 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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" 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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" 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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" [[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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" 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/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" dependencies = [ "bitflags", "core-foundation", diff --git a/Cargo.toml b/Cargo.toml index 83f3ad985a..e062eb99eb 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 this is merged: https://github.com/servo/core-foundation-rs/pull/454 +cocoa = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +cocoa-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +core-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +core-graphics = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs index b77cb2ee51..849f112d45 100644 --- a/gpui/src/platform/mac/app.rs +++ b/gpui/src/platform/mac/app.rs @@ -1,9 +1,13 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use crate::{executor, platform}; use anyhow::Result; -use cocoa::{appkit::NSApplication, base::nil}; +use cocoa::{ + appkit::{NSApplication, NSOpenPanel, NSModalResponse}, + base::nil, + foundation::{NSArray, NSString, NSURL}, +}; use objc::{msg_send, sel, sel_impl}; -use std::{rc::Rc, sync::Arc}; +use std::{path::PathBuf, rc::Rc, sync::Arc}; pub struct App { dispatcher: Arc, @@ -39,6 +43,37 @@ impl platform::App for App { 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() } diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index bbd2dc9838..9ece82fb78 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -41,6 +41,7 @@ pub trait App { options: WindowOptions, executor: Rc, ) -> Result>; + fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; fn fonts(&self) -> Arc; fn quit(&self); } @@ -66,6 +67,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 067184adea..fdb497ae97 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -48,6 +48,10 @@ impl super::App for App { } fn quit(&self) {} + + fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option> { + None + } } impl Window { diff --git a/zed/src/main.rs b/zed/src/main.rs index 08fb098784..82a8a73106 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,5 +1,5 @@ use fs::OpenOptions; -use gpui::platform::{current as platform, Runner as _}; +use gpui::platform::{current as platform, PathPromptOptions, Runner as _}; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; @@ -18,9 +18,24 @@ fn main() { .set_menus(menus::MENUS) .on_menu_command({ let app = app.clone(); - move |command| { - log::info!("menu command: {}", command); - app.dispatch_global_action(command, ()) + let settings_rx = settings_rx.clone(); + move |command| match command { + "app:open" => { + if let Some(paths) = app.platform().prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }) { + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths, + settings: settings_rx.clone(), + }, + ); + } + } + _ => app.dispatch_global_action(command, ()), } }) .on_finish_launching({ diff --git a/zed/src/menus.rs b/zed/src/menus.rs index c6ef18b746..cda749c30e 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -6,7 +6,7 @@ pub const MENUS: &'static [Menu] = &[ name: "Zed", items: &[ MenuItem::Action { - name: "About Zed...", + name: "About Zed…", keystroke: None, action: "app:about-zed", }, @@ -20,6 +20,14 @@ pub const MENUS: &'static [Menu] = &[ }, Menu { name: "File", + items: &[MenuItem::Action { + name: "Open…", + keystroke: Some("cmd-o"), + action: "app:open", + }], + }, + Menu { + name: "Edit", items: &[ MenuItem::Action { name: "Undo", From 6873662c472561e228dfb90409f656be2705be89 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 9 Apr 2021 08:45:23 -0700 Subject: [PATCH 06/15] Use upstream git revision of core-foundation-rs --- Cargo.lock | 12 ++++++------ Cargo.toml | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce7dedfdf6..b497ce1981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "cocoa" version = "0.24.0" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "cocoa-foundation" version = "0.1.0" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "block", @@ -443,7 +443,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" version = "0.9.1" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "core-foundation-sys", "libc", @@ -452,12 +452,12 @@ dependencies = [ [[package]] name = "core-foundation-sys" version = "0.8.2" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" [[package]] name = "core-graphics" version = "0.22.2" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ "bitflags", "core-foundation", @@ -469,7 +469,7 @@ dependencies = [ [[package]] name = "core-graphics-types" version = "0.1.1" -source = "git+https://github.com/zed-industries/core-foundation-rs?rev=52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b#52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b" +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 e062eb99eb..b60e33d042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ members = ["zed", "gpui"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -# TODO - Remove when this is merged: https://github.com/servo/core-foundation-rs/pull/454 -cocoa = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -cocoa-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -core-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} -core-graphics = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "52b7bc0a7026dbbe0da66f5e8de6ce1031476d5b"} +# 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"} From 301163bab70b289ca9dc5865179bd69bdb1a3373 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 9 Apr 2021 13:03:26 -0600 Subject: [PATCH 07/15] Add lifecycle methods to Platform trait Co-Authored-By: Max Brunsfeld --- gpui/src/app.rs | 12 +- gpui/src/platform/mac/app.rs | 100 -------- gpui/src/platform/mac/mod.rs | 11 +- gpui/src/platform/mac/platform.rs | 387 ++++++++++++++++++++++++++++++ gpui/src/platform/mod.rs | 13 +- gpui/src/platform/test.rs | 31 ++- 6 files changed, 433 insertions(+), 121 deletions(-) delete mode 100644 gpui/src/platform/mac/app.rs create mode 100644 gpui/src/platform/mac/platform.rs diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 63f52f77b1..14380f19e1 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, Platform as _, WindowOptions}, presenter::Presenter, util::post_inc, AssetCache, AssetSource, FontCache, TextLayoutCache, @@ -88,7 +88,7 @@ impl App { asset_source: A, f: G, ) -> 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( foreground.clone(), @@ -269,7 +269,7 @@ impl App { self.0.borrow().font_cache.clone() } - pub fn platform(&self) -> Arc { + pub fn platform(&self) -> Arc { self.0.borrow().platform.clone() } } @@ -309,7 +309,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext); pub struct MutableAppContext { weak_self: Option>>, - platform: Arc, + platform: Arc, font_cache: Arc, assets: Arc, ctx: AppContext, @@ -337,7 +337,7 @@ pub struct MutableAppContext { impl MutableAppContext { pub fn new( foreground: Rc, - platform: Arc, + platform: Arc, asset_source: impl AssetSource, ) -> Self { let fonts = platform.fonts(); @@ -381,7 +381,7 @@ impl MutableAppContext { &self.ctx } - pub fn platform(&self) -> Arc { + pub fn platform(&self) -> Arc { self.platform.clone() } diff --git a/gpui/src/platform/mac/app.rs b/gpui/src/platform/mac/app.rs deleted file mode 100644 index 1965c634a3..0000000000 --- a/gpui/src/platform/mac/app.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::{BoolExt as _, Dispatcher, FontSystem, Window}; -use crate::{executor, platform}; -use anyhow::Result; -use cocoa::{ - appkit::{NSApplication, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString}, - base::nil, - foundation::{NSArray, NSData, NSString, NSURL}, -}; -use objc::{msg_send, sel, sel_impl}; -use std::{ffi::c_void, path::PathBuf, rc::Rc, sync::Arc}; - -pub struct App { - dispatcher: Arc, - 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 = 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); - } - } -} diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index b7a19c3647..b54d148682 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/gpui/src/platform/mac/mod.rs @@ -1,27 +1,26 @@ -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; +use platform::MacPlatform; pub use runner::Runner; use window::Window; -pub fn app() -> impl platform::App { - App::new() +pub fn app() -> impl super::Platform { + MacPlatform::new() } -pub fn runner() -> impl platform::Runner { +pub fn runner() -> impl super::Runner { Runner::new() } diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs new file mode 100644 index 0000000000..d089ba26d2 --- /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 = "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>(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(), + } + } +} + +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 pool = NSAutoreleasePool::new(nil); + let app: id = msg_send![APP_CLASS, sharedApplication]; + let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; + + let self_ptr = self as *const Self as *mut c_void; + (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); + (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); + app.setDelegate_(app_delegate); + 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)); + } + } +} + +impl MacPlatform { + 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 + } +} + +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/mod.rs b/gpui/src/platform/mod.rs index ae0f3cddb9..b96bffb979 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -33,8 +33,17 @@ pub trait Runner { fn run(self); } -pub trait App { +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 ()>); + fn dispatcher(&self) -> Arc; + fn fonts(&self) -> Arc; + fn activate(&self, ignoring_other_apps: bool); fn open_window( &self, @@ -42,9 +51,9 @@ pub trait App { executor: Rc, ) -> Result>; fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; - fn fonts(&self) -> Arc; fn quit(&self); fn copy(&self, text: &str); + fn set_menus(&self, menus: &[Menu]); } pub trait Dispatcher: Send + Sync { diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 9f719bd2bc..08ec95ca74 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, } @@ -19,7 +19,7 @@ pub struct Window { pub struct WindowContext {} -impl App { +impl Platform { fn new() -> Self { Self { dispatcher: Arc::new(Dispatcher), @@ -28,11 +28,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 +61,7 @@ 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) {} @@ -102,6 +119,6 @@ impl super::Window for Window { } } -pub fn app() -> impl super::App { - App::new() +pub fn platform() -> impl super::Platform { + Platform::new() } From 4ecc17b1bb878cfe6e868a815b04dd4fc18e302d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 9 Apr 2021 13:38:09 -0600 Subject: [PATCH 08/15] WIP: Make App the only entry point from main Co-Authored-By: Max Brunsfeld --- gpui/src/app.rs | 158 +++++++++++++++++++++------- zed/src/editor/buffer_view.rs | 5 +- zed/src/file_finder.rs | 6 +- zed/src/main.rs | 76 ++++++------- zed/src/workspace/mod.rs | 2 +- zed/src/workspace/pane.rs | 4 +- zed/src/workspace/workspace_view.rs | 2 +- 7 files changed, 165 insertions(+), 88 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 14380f19e1..2c7af63af8 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -21,6 +21,7 @@ use std::{ fmt::{self, Debug}, hash::{Hash, Hasher}, marker::PhantomData, + path::PathBuf, rc::{self, Rc}, sync::{Arc, Weak}, }; @@ -83,20 +84,44 @@ pub enum MenuItem<'a> { #[derive(Clone)] pub struct App(Rc>); +pub trait TestClosure<'a, T> { + type Result: 'a + Future; + fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result; +} + +impl<'a, F, R, T> TestClosure<'a, T> for F +where + F: FnOnce(&mut MutableAppContext) -> R, + R: 'a + Future, +{ + type Result = R; + + fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result { + (self)(ctx) + } +} + impl App { - pub fn test, G: FnOnce(App) -> F>( + pub fn test< + T, + A: AssetSource, + // F: 'static + , + // G: for<'a> FnOnce(&'a mut MutableAppContext) -> impl Future, + G: for<'a> TestClosure<'a, T>, + >( asset_source: A, f: G, ) -> T { 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.clone(), Arc::new(platform), asset_source, - )))); - app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); - smol::block_on(foreground.run(f(app))) + ))); + ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx)); + let mut ctx = ctx.borrow_mut(); + smol::block_on(foreground.run(f.run_test(&mut *ctx))) } pub fn new(asset_source: impl AssetSource) -> Result { @@ -111,6 +136,80 @@ impl App { Ok(app) } + pub fn on_become_active(self, mut callback: F) -> Self + where + F: 'static + FnMut(&mut MutableAppContext), + { + let ctx = self.0.clone(); + self.0 + .borrow() + .platform + .on_become_active(Box::new(move || callback(&mut *ctx.borrow_mut()))); + self + } + + pub fn on_resign_active(self, mut callback: F) -> Self + where + F: 'static + FnMut(&mut MutableAppContext), + { + let ctx = self.0.clone(); + self.0 + .borrow() + .platform + .on_resign_active(Box::new(move || callback(&mut *ctx.borrow_mut()))); + self + } + + pub fn on_event(self, mut callback: F) -> Self + where + F: 'static + FnMut(Event, &mut MutableAppContext) -> bool, + { + let ctx = self.0.clone(); + self.0.borrow().platform.on_event(Box::new(move |event| { + callback(event, &mut *ctx.borrow_mut()) + })); + self + } + + pub fn on_menu_command(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 run(self, callback: F) + where + F: 'static + FnOnce(&mut MutableAppContext), + { + let ctx = self.0.clone(); + self.0 + .borrow() + .platform + .run(Box::new(move || callback(&mut *ctx.borrow_mut()))); + } + pub fn on_window_invalidated( &self, window_id: usize, @@ -155,12 +254,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); } @@ -556,14 +649,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() { @@ -574,7 +671,7 @@ impl MutableAppContext { } } - fn add_bindings>(&mut self, bindings: T) { + pub fn add_bindings>(&mut self, bindings: T) { self.keystroke_matcher.add_bindings(bindings); } @@ -1887,13 +1984,6 @@ impl ModelHandle { 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 update(&self, app: &mut A, update: F) -> S where A: UpdateModel, @@ -2362,9 +2452,7 @@ mod tests { } } - App::test((), |mut app| async move { - let app = &mut app; - + App::test((), |app: &mut MutableAppContext| async move { 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); @@ -2375,19 +2463,15 @@ 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.as_ref(app).events, vec!["updated".to_string()]); + assert_eq!( + handle_2.as_ref(app).events, + vec![ + "observed event 1".to_string(), + "notified".to_string(), + "observed event 2".to_string(), + ] + ); handle_2.update(app, |model, _| { drop(handle_1); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 6f3d6f108e..fbd6c8cc83 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -7,7 +7,8 @@ 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, + ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext, + WeakViewHandle, }; use gpui::{geometry::vector::Vector2F, TextLayoutCache}; use parking_lot::Mutex; @@ -24,7 +25,7 @@ use std::{ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); -pub fn init(app: &mut App) { +pub fn init(app: &mut MutableAppContext) { app.add_bindings(vec![ Binding::new("backspace", "buffer:backspace", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index c951d12934..28a0ae0360 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, + App, 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); diff --git a/zed/src/main.rs b/zed/src/main.rs index 82a8a73106..f7e4f0346b 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -13,55 +13,47 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); - - platform::runner() - .set_menus(menus::MENUS) - .on_menu_command({ - let app = app.clone(); - let settings_rx = settings_rx.clone(); - move |command| match command { - "app:open" => { - if let Some(paths) = app.platform().prompt_for_paths(PathPromptOptions { - files: true, - directories: true, - multiple: true, - }) { - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths, - settings: settings_rx.clone(), - }, - ); - } - } - _ => app.dispatch_global_action(command, ()), - } - }) - .on_finish_launching({ - let mut app = app.clone(); - move || { - workspace::init(&mut app); - editor::init(&mut app); - file_finder::init(&mut app); - - if stdout_is_a_pty() { - app.platform().activate(true); - } - - let paths = collect_path_args(); - if !paths.is_empty() { - app.dispatch_global_action( + app.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/workspace/mod.rs b/zed/src/workspace/mod.rs index b7a76f9445..e1a087049c 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -12,7 +12,7 @@ use crate::{settings::Settings, watch}; use gpui::{App, 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); diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index fad299aa8b..903292a63e 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, + App, 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_view.rs b/zed/src/workspace/workspace_view.rs index 5810245be4..32e16fc014 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -8,7 +8,7 @@ use gpui::{ 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![ From 079050541f1858b6019aecef02ce3d978056b2e2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 9 Apr 2021 15:58:19 -0700 Subject: [PATCH 09/15] Get app running and test passing after gpui App+Platform restructure --- gpui/examples/text.rs | 14 +- gpui/src/app.rs | 325 ++++++++++++------------- gpui/src/platform/mac/mod.rs | 9 +- gpui/src/platform/mac/platform.rs | 200 +++++++-------- gpui/src/platform/mod.rs | 13 +- gpui/src/platform/test.rs | 7 +- zed/src/editor/buffer/mod.rs | 18 +- zed/src/editor/buffer_view.rs | 206 ++++++++-------- zed/src/editor/display_map/fold_map.rs | 279 ++++++++++----------- zed/src/editor/display_map/mod.rs | 91 ++++--- zed/src/file_finder.rs | 47 ++-- zed/src/main.rs | 8 +- zed/src/workspace/mod.rs | 28 ++- zed/src/workspace/pane.rs | 2 +- zed/src/workspace/workspace.rs | 50 ++-- zed/src/workspace/workspace_view.rs | 113 +++++---- zed/src/worktree/worktree.rs | 58 ++--- 17 files changed, 719 insertions(+), 749 deletions(-) diff --git a/gpui/examples/text.rs b/gpui/examples/text.rs index 14bc198270..2dad6e0786 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,12 @@ 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(); + let app = gpui::App::new(()).unwrap(); + app.on_finish_launching(|app| { + app.platform().activate(true); + app.add_window(|_| TextView); + }) + .run(); } struct TextView; diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 2c7af63af8..ab3f471e73 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, Platform as _, WindowOptions}, + platform::{self, WindowOptions}, presenter::Presenter, util::post_inc, AssetCache, AssetSource, FontCache, TextLayoutCache, @@ -84,33 +84,10 @@ pub enum MenuItem<'a> { #[derive(Clone)] pub struct App(Rc>); -pub trait TestClosure<'a, T> { - type Result: 'a + Future; - fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result; -} - -impl<'a, F, R, T> TestClosure<'a, T> for F -where - F: FnOnce(&mut MutableAppContext) -> R, - R: 'a + Future, -{ - type Result = R; - - fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result { - (self)(ctx) - } -} - impl App { - pub fn test< - T, - A: AssetSource, - // F: 'static + , - // G: for<'a> FnOnce(&'a mut MutableAppContext) -> impl Future, - G: for<'a> TestClosure<'a, T>, - >( + pub fn test T>( asset_source: A, - f: G, + f: F, ) -> T { let platform = platform::test::platform(); let foreground = Rc::new(executor::Foreground::test()); @@ -121,11 +98,35 @@ impl App { ))); ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx)); let mut ctx = ctx.borrow_mut(); - smol::block_on(foreground.run(f.run_test(&mut *ctx))) + f(&mut *ctx) + } + + pub fn test_async<'a, T, F, A: AssetSource, Fn>(asset_source: A, f: Fn) -> T + where + Fn: FnOnce(&'a mut MutableAppContext) -> F, + F: Future + 'a, + { + let platform = platform::test::platform(); + let foreground = Rc::new(executor::Foreground::test()); + let ctx = Rc::new(RefCell::new(MutableAppContext::new( + foreground.clone(), + Arc::new(platform), + asset_source, + ))); + let mut ctx_ref = ctx.borrow_mut(); + ctx_ref.weak_self = Some(Rc::downgrade(&ctx)); + let ctx = &mut *ctx_ref; + + // TODO - is there a better way of getting this to compile? + let ctx = unsafe { std::mem::transmute(ctx) }; + let future = f(ctx); + + drop(ctx_ref); + 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, @@ -199,7 +200,7 @@ impl App { self } - pub fn run(self, callback: F) + pub fn on_finish_launching(self, callback: F) -> Self where F: 'static + FnOnce(&mut MutableAppContext), { @@ -207,7 +208,16 @@ impl App { self.0 .borrow() .platform - .run(Box::new(move || callback(&mut *ctx.borrow_mut()))); + .on_finish_launching(Box::new(move || callback(&mut *ctx.borrow_mut()))); + self + } + + pub fn set_menus(&self, menus: &[Menu]) { + self.0.borrow().platform.set_menus(menus); + } + + pub fn run(self) { + platform::current::run(); } pub fn on_window_invalidated( @@ -246,7 +256,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, @@ -280,15 +290,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, @@ -345,15 +346,6 @@ impl App { 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() } @@ -478,6 +470,10 @@ impl MutableAppContext { self.platform.clone() } + pub fn font_cache(&self) -> &Arc { + &self.font_cache + } + pub fn foreground_executor(&self) -> &Rc { &self.foreground } @@ -598,7 +594,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], @@ -709,7 +722,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, @@ -796,7 +809,7 @@ impl MutableAppContext { .borrow_mut() .dispatch_event(event, ctx.downgrade()); for action in actions { - ctx.dispatch_action( + ctx.dispatch_action_any( window_id, &action.path, action.name, @@ -1328,6 +1341,12 @@ impl UpdateView for MutableAppContext { } } +impl AsRef for MutableAppContext { + fn as_ref(&self) -> &AppContext { + &self.ctx + } +} + pub struct AppContext { models: HashMap>, windows: HashMap, @@ -2112,13 +2131,6 @@ impl ViewHandle { 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 update(&self, app: &mut A, update: F) -> S where A: UpdateView, @@ -2452,10 +2464,10 @@ mod tests { } } - App::test((), |app: &mut MutableAppContext| async move { + 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()); @@ -2478,11 +2490,10 @@ mod tests { 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] @@ -2496,8 +2507,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(); @@ -2513,10 +2523,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.as_ref(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.as_ref(app).events, vec![7, 10, 5]); }) } @@ -2532,8 +2542,7 @@ 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(); @@ -2551,13 +2560,13 @@ mod tests { model.count = 7; c.notify() }); - handle_1.read(app, |model, _| assert_eq!(model.events, vec![7])); + assert_eq!(handle_1.as_ref(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.as_ref(app).events, vec![7, 10, 5]) }) } @@ -2572,25 +2581,25 @@ mod tests { type Event = (); } - App::test((), |mut app| async move { + App::test_async((), |app| async move { let handle = app.add_model(|_| Model::default()); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn(async { 7 }, |model, output, _| { model.count = output; }) }) .await; - handle.read(&app, |model, _| assert_eq!(model.count, 7)); + assert_eq!(handle.as_ref(app).count, 7); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn(async { 14 }, |model, output, _| { model.count = output; }) }) .await; - handle.read(&app, |model, _| assert_eq!(model.count, 14)); + assert_eq!(handle.as_ref(app).count, 14); }); } @@ -2605,10 +2614,10 @@ mod tests { type Event = (); } - App::test((), |mut app| async move { + App::test_async((), |app| async move { let handle = app.add_model(|_| Model::default()); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn_stream( smol::stream::iter(vec![1, 2, 3]), |model, output, _| { @@ -2620,10 +2629,7 @@ mod tests { ) }) .await; - - handle.read(&app, |model, _| { - assert_eq!(model.events, [Some(1), Some(2), Some(3), None]) - }); + assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None]) }) } @@ -2662,40 +2668,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.as_ref(app).events, vec!["updated".to_string()]); + assert_eq!( + handle_2.as_ref(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()); }) } @@ -2726,8 +2726,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(); @@ -2748,13 +2747,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.as_ref(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.as_ref(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.as_ref(app).events, vec![7, 10, 5, 9]); }) } @@ -2782,9 +2781,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); @@ -2799,7 +2796,7 @@ mod tests { ctx.subscribe(&observed_model, |_, _, _| {}); }); - app.update(|_| { + app.update(|| { drop(observing_view); drop(observing_model); }); @@ -2839,8 +2836,7 @@ 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()); @@ -2854,7 +2850,7 @@ mod tests { model.count = 11; c.notify(); }); - view.read(app, |view, _| assert_eq!(view.events, vec![11])); + assert_eq!(view.as_ref(app).events, vec![11]); }) } @@ -2882,9 +2878,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); @@ -2897,7 +2891,7 @@ mod tests { ctx.observe(&observed_model, |_, _, _| {}); }); - app.update(|_| { + app.update(|| { drop(observing_view); drop(observing_model); }); @@ -2937,8 +2931,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()); @@ -2953,18 +2946,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.as_ref(app).events, + [ + "self focused".to_string(), + "self blurred".to_string(), + "view 2 focused".to_string(), + "self focused".to_string(), + "view 2 blurred".to_string(), + ], + ); }) } @@ -2989,24 +2980,24 @@ mod tests { } } - App::test((), |mut app| async move { + App::test_async((), |app| async move { let (_, handle) = app.add_window(|_| View::default()); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn(async { 7 }, |me, output, _| { me.count = output; }) }) .await; - handle.read(&app, |view, _| assert_eq!(view.count, 7)); + assert_eq!(handle.as_ref(app).count, 7); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn(async { 14 }, |me, output, _| { me.count = output; }) }) .await; - handle.read(&app, |view, _| assert_eq!(view.count, 14)); + assert_eq!(handle.as_ref(app).count, 14); }); } @@ -3031,10 +3022,10 @@ mod tests { } } - App::test((), |mut app| async move { + App::test_async((), |app| async move { let (_, handle) = app.add_window(|_| View::default()); handle - .update(&mut app, |_, c| { + .update(app, |_, c| { c.spawn_stream( smol::stream::iter(vec![1_usize, 2, 3]), |me, output, _| { @@ -3047,9 +3038,7 @@ mod tests { }) .await; - handle.read(&app, |view, _| { - assert_eq!(view.events, [Some(1), Some(2), Some(3), None]) - }); + assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None]) }); } @@ -3095,7 +3084,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(); @@ -3169,7 +3158,7 @@ mod tests { } #[test] - fn test_dispatch_keystroke() -> Result<()> { + fn test_dispatch_keystroke() { use std::cell::Cell; #[derive(Clone)] @@ -3209,7 +3198,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); @@ -3238,12 +3227,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] @@ -3266,7 +3255,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 }); @@ -3293,7 +3282,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); @@ -3304,7 +3293,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); @@ -3312,7 +3301,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(); @@ -3351,49 +3340,49 @@ mod tests { type Event = (); } - App::test((), |mut app| async move { + App::test_async((), |app| async move { let model = app.add_model(|_| Model); let (_, view) = app.add_window(|_| View); - model.update(&mut app, |_, ctx| { + model.update(app, |_, ctx| { ctx.spawn(async {}, |_, _, _| {}).detach(); // Cancel this task drop(ctx.spawn(async {}, |_, _, _| {})); }); - view.update(&mut app, |_, ctx| { + view.update(app, |_, ctx| { ctx.spawn(async {}, |_, _, _| {}).detach(); // Cancel this task drop(ctx.spawn(async {}, |_, _, _| {})); }); - assert!(!app.0.borrow().future_handlers.borrow().is_empty()); + assert!(!app.future_handlers.borrow().is_empty()); app.finish_pending_tasks().await; - assert!(app.0.borrow().future_handlers.borrow().is_empty()); + assert!(app.future_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks - model.update(&mut app, |_, ctx| { + model.update(app, |_, ctx| { ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) .detach(); // Cancel this task drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); }); - view.update(&mut app, |_, ctx| { + view.update(app, |_, ctx| { ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) .detach(); // Cancel this task drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); }); - assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); + assert!(!app.stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; - assert!(app.0.borrow().stream_handlers.borrow().is_empty()); + assert!(app.stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks // Tasks are considered finished when we drop handles let mut tasks = Vec::new(); - model.update(&mut app, |_, ctx| { + model.update(app, |_, ctx| { tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn_stream( smol::stream::iter(vec![1, 2, 3]), @@ -3402,7 +3391,7 @@ mod tests { ))); }); - view.update(&mut app, |_, ctx| { + view.update(app, |_, ctx| { tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn_stream( smol::stream::iter(vec![1, 2, 3]), @@ -3411,12 +3400,12 @@ mod tests { ))); }); - assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); + assert!(!app.stream_handlers.borrow().is_empty()); let finish_pending_tasks = app.finish_pending_tasks(); drop(tasks); finish_pending_tasks.await; - assert!(app.0.borrow().stream_handlers.borrow().is_empty()); + assert!(app.stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks }); } diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index b54d148682..07ada3650a 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/gpui/src/platform/mac/mod.rs @@ -5,7 +5,6 @@ mod fonts; mod geometry; mod platform; mod renderer; -mod runner; mod sprite_cache; mod window; @@ -13,15 +12,15 @@ use cocoa::base::{BOOL, NO, YES}; pub use dispatcher::Dispatcher; pub use fonts::FontSystem; use platform::MacPlatform; -pub use runner::Runner; +use std::sync::Arc; use window::Window; -pub fn app() -> impl super::Platform { +pub fn platform() -> Arc { MacPlatform::new() } -pub fn runner() -> impl super::Runner { - Runner::new() +pub fn run() { + MacPlatform::run(); } trait BoolExt { diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs index d089ba26d2..40bd3566a5 100644 --- a/gpui/src/platform/mac/platform.rs +++ b/gpui/src/platform/mac/platform.rs @@ -90,13 +90,112 @@ struct Callbacks { } impl MacPlatform { - pub fn new() -> Self { - Self { + pub fn new() -> Arc { + let result = Arc::new(Self { dispatcher: Arc::new(Dispatcher), fonts: Arc::new(FontSystem::new()), callbacks: Default::default(), menu_item_actions: Default::default(), + }); + + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; + let self_ptr = result.as_ref() as *const Self as *const c_void; + app.setDelegate_(app_delegate); + (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); + (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); } + + result + } + + pub fn run() { + unsafe { + let pool = NSAutoreleasePool::new(nil); + let app: id = msg_send![APP_CLASS, sharedApplication]; + + app.run(); + pool.drain(); + (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); + (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); + } + } + + 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 } } @@ -121,23 +220,8 @@ impl platform::Platform for MacPlatform { 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 pool = NSAutoreleasePool::new(nil); - let app: id = msg_send![APP_CLASS, sharedApplication]; - let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - - let self_ptr = self as *const Self as *mut c_void; - (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); - (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); - app.setDelegate_(app_delegate); - app.run(); - pool.drain(); - (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); - (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); - } + fn on_finish_launching(&self, callback: Box ()>) { + self.callbacks.borrow_mut().finish_launching = Some(callback); } fn dispatcher(&self) -> Arc { @@ -222,84 +306,6 @@ impl platform::Platform for MacPlatform { } } -impl MacPlatform { - 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 - } -} - 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()); diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index b96bffb979..6e5d53585f 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -22,24 +22,13 @@ 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; - fn on_menu_command(self, callback: F) -> Self; - 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 set_menus(self, menus: &[Menu]) -> 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 ()>); + fn on_finish_launching(&self, callback: Box ()>); fn dispatcher(&self) -> Arc; fn fonts(&self) -> Arc; diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 08ec95ca74..1cd4399c1b 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -39,9 +39,7 @@ impl super::Platform for Platform { fn on_open_files(&self, _: Box)>) {} - fn run(&self, _on_finish_launching: Box ()>) { - unimplemented!() - } + fn on_finish_launching(&self, _: Box ()>) {} fn dispatcher(&self) -> Arc { self.dispatcher.clone() @@ -61,8 +59,7 @@ impl super::Platform for Platform { Ok(Box::new(Window::new(options.bounds.size()))) } - fn set_menus(&self, _menus: &[crate::Menu]) { - } + fn set_menus(&self, _menus: &[crate::Menu]) {} fn quit(&self) {} diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 827da6be9d..9807b8ef97 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2193,13 +2193,13 @@ mod tests { #[test] fn test_edit_events() { - App::test((), |mut app| async move { + App::test((), |app| { let buffer_1_events = Rc::new(RefCell::new(Vec::new())); let buffer_2_events = Rc::new(RefCell::new(Vec::new())); let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef")); let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef")); - let ops = buffer1.update(&mut app, |buffer, ctx| { + let ops = buffer1.update(app, |buffer, ctx| { let buffer_1_events = buffer_1_events.clone(); ctx.subscribe(&buffer1, move |_, event, _| { buffer_1_events.borrow_mut().push(event.clone()) @@ -2211,7 +2211,7 @@ mod tests { buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap() }); - buffer2.update(&mut app, |buffer, ctx| { + buffer2.update(app, |buffer, ctx| { buffer.apply_ops(ops, Some(ctx)).unwrap(); }); @@ -2715,12 +2715,12 @@ mod tests { #[test] fn test_is_modified() -> Result<()> { - App::test((), |mut app| async move { + App::test((), |app| { let model = app.add_model(|_| Buffer::new(0, "abc")); let events = Rc::new(RefCell::new(Vec::new())); // initially, the buffer isn't dirty. - model.update(&mut app, |buffer, ctx| { + model.update(app, |buffer, ctx| { ctx.subscribe(&model, { let events = events.clone(); move |_, event, _| events.borrow_mut().push(event.clone()) @@ -2733,7 +2733,7 @@ mod tests { }); // after the first edit, the buffer is dirty, and emits a dirtied event. - model.update(&mut app, |buffer, ctx| { + model.update(app, |buffer, ctx| { assert!(buffer.text() == "ac"); assert!(buffer.is_dirty()); assert_eq!( @@ -2752,7 +2752,7 @@ mod tests { }); // after saving, the buffer is not dirty, and emits a saved event. - model.update(&mut app, |buffer, ctx| { + model.update(app, |buffer, ctx| { assert!(!buffer.is_dirty()); assert_eq!(*events.borrow(), &[Event::Saved]); events.borrow_mut().clear(); @@ -2762,7 +2762,7 @@ mod tests { }); // after editing again, the buffer is dirty, and emits another dirty event. - model.update(&mut app, |buffer, ctx| { + model.update(app, |buffer, ctx| { assert!(buffer.text() == "aBDc"); assert!(buffer.is_dirty()); assert_eq!( @@ -2788,7 +2788,7 @@ mod tests { assert!(buffer.is_dirty()); }); - model.update(&mut app, |_, _| { + model.update(app, |_, _| { assert_eq!( *events.borrow(), &[Event::Edited(vec![Edit { diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index fbd6c8cc83..b7974a3a2e 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -6,7 +6,7 @@ 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, + fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext, WeakViewHandle, }; @@ -1240,112 +1240,125 @@ mod tests { use super::*; use crate::{editor::Point, settings, test::sample_text}; use anyhow::Error; + use gpui::App; use unindent::Unindent; #[test] fn test_selection_with_mouse() { - App::test((), |mut app| async move { + App::test((), |app| { let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, buffer_view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); - buffer_view.update(&mut app, |view, ctx| { + buffer_view.update(app, |view, ctx| { view.begin_selection(DisplayPoint::new(2, 2), false, ctx); }); - buffer_view.read(&app, |view, app| { - let selections = view - .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) - .collect::>(); - assert_eq!( - selections, - [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] - ); - }); + let view = buffer_view.as_ref(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.as_ref(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.as_ref(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.as_ref(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.as_ref(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.as_ref(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 +1366,17 @@ mod tests { let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); - view.read(&app, |view, app| { - let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?; - assert_eq!(layouts.len(), 6); - Result::<()>::Ok(()) - })?; - - Ok(()) + let layouts = view + .as_ref(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 +1404,9 @@ mod tests { let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); - view.update(&mut app, |view, ctx| { - view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)?; + view.update(app, |view, ctx| { + view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx) + .unwrap(); view.fold(&(), ctx); assert_eq!( view.text(ctx.app()), @@ -1449,23 +1461,19 @@ mod tests { view.unfold(&(), ctx); assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text()); - - Ok::<(), Error>(()) - })?; - - Ok(()) - }) + }); + }); } #[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 +1484,7 @@ mod tests { ) })?; - view.update(&mut app, |view, ctx| { + view.update(app, |view, ctx| { view.move_down(&(), ctx); assert_eq!( view.selections(ctx.app()), @@ -1495,8 +1503,8 @@ mod tests { } #[test] - fn test_backspace() -> Result<()> { - App::test((), |mut app| async move { + fn test_backspace() { + App::test((), |app| { let buffer = app.add_model(|_| { Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n") }); @@ -1504,7 +1512,7 @@ mod tests { let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); - view.update(&mut app, |view, ctx| -> Result<()> { + view.update(app, |view, ctx| { view.select_ranges( &[ // an empty selection - the preceding character is deleted @@ -1515,17 +1523,15 @@ mod tests { DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), ], ctx, - )?; + ) + .unwrap(); view.backspace(&(), ctx); - Ok(()) - })?; + }); - buffer.read(&mut app, |buffer, _| -> Result<()> { - assert_eq!(buffer.text(), "oe two three\nfou five six\nseven ten\n"); - Ok(()) - })?; - - Ok(()) + assert_eq!( + buffer.as_ref(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..d67b4b510e 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -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,15 +588,15 @@ 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 fold_count = rng.gen_range(0..10); @@ -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.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.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 { diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 2679766aa1..7bc17627dd 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -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.as_ref(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.as_ref(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 28a0ae0360..d03ce43294 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -11,7 +11,7 @@ use gpui::{ fonts::{Properties, Weight}, geometry::vector::vec2f, keymap::{self, Binding}, - App, AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext, + AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, WeakViewHandle, }; use std::cmp; @@ -394,20 +394,23 @@ 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((), |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(); + super::init(app); + editor::init(app); 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 +423,15 @@ mod tests { "file_finder:toggle".into(), (), ); - let (finder, query_buffer) = workspace_view.read(&app, |view, ctx| { - let finder = view - .modal() - .cloned() - .unwrap() - .downcast::() - .unwrap(); - let query_buffer = finder.as_ref(ctx).query_buffer.clone(); - (finder, query_buffer) - }); + + let finder = workspace_view + .as_ref(app) + .modal() + .cloned() + .unwrap() + .downcast::() + .unwrap(); + let query_buffer = finder.as_ref(app).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 +454,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 +464,6 @@ mod tests { // }] // } // ); - Ok(()) - }) + }); } } diff --git a/zed/src/main.rs b/zed/src/main.rs index f7e4f0346b..6d171a5a29 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,5 +1,5 @@ use fs::OpenOptions; -use gpui::platform::{current as platform, PathPromptOptions, Runner as _}; +use gpui::platform::PathPromptOptions; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; @@ -13,6 +13,7 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); + app.set_menus(menus::MENUS); app.on_menu_command({ let settings_rx = settings_rx.clone(); move |command, ctx| match command { @@ -34,7 +35,7 @@ fn main() { _ => ctx.dispatch_global_action(command, ()), } }) - .run(move |ctx| { + .on_finish_launching(move |ctx| { workspace::init(ctx); editor::init(ctx); file_finder::init(ctx); @@ -53,7 +54,8 @@ fn main() { }, ); } - }); + }) + .run(); } fn init_logger() { diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index e1a087049c..91dd99419a 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -9,7 +9,7 @@ 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 MutableAppContext) { @@ -64,10 +64,10 @@ mod tests { #[test] fn test_open_paths_action() { - App::test((), |mut app| async move { + App::test((), |app| { let settings = settings::channel(&app.font_cache()).unwrap().1; - init(&mut app); + init(app); let dir = temp_tree(json!({ "a": { @@ -94,7 +94,7 @@ mod tests { settings: settings.clone(), }, ); - assert_eq!(app.window_ids().len(), 1); + assert_eq!(app.window_ids().count(), 1); app.dispatch_global_action( "workspace:open_paths", @@ -103,11 +103,19 @@ mod tests { settings: settings.clone(), }, ); - assert_eq!(app.window_ids().len(), 1); - let workspace_view_1 = app.root_view::(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 + .as_ref(app) + .workspace + .as_ref(app) + .worktrees() + .len(), + 2 + ); app.dispatch_global_action( "workspace:open_paths", @@ -119,7 +127,7 @@ mod tests { settings: settings.clone(), }, ); - assert_eq!(app.window_ids().len(), 2); + assert_eq!(app.window_ids().count(), 2); }); } } diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 903292a63e..58acb86abb 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -5,7 +5,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, keymap::Binding, - App, AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext, + AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext, }; use std::cmp; diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 092144791c..cc5cfec602 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -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: &mut MutableAppContext) -> 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: &mut MutableAppContext) -> Vec<(usize, usize)> { + self.as_ref(app) + .worktrees() + .iter() + .flat_map(|tree| { + let tree_id = tree.id(); + tree.as_ref(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((), |app| async move { let dir = temp_tree(json!({ "a": { "aa": "aa contents", @@ -241,32 +240,29 @@ 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 = workspace.as_ref(app).worktrees.iter().next().unwrap(); + let entry_id = tree.as_ref(app).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| { + let (future_1, future_2) = workspace.update(app, |w, app| { ( w.open_entry(entry, app).unwrap(), w.open_entry(entry, app).unwrap(), ) }); - 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?; + .update(app, |w, app| w.open_entry(entry, app).unwrap()) + .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 32e16fc014..aaf1e9eb9a 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -2,8 +2,8 @@ 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}; @@ -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((), |app| async move { let dir = temp_tree(json!({ "a": { "aa": "aa contents", @@ -407,64 +406,70 @@ 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 = workspace.file_entries(app); let (_, workspace_view) = app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); // Open the first entry - workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(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); - }); + assert_eq!( + workspace_view + .as_ref(app) + .active_pane() + .as_ref(app) + .items() + .len(), + 1 + ); // Open the second entry - workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx)); + workspace_view.update(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); - assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(app), - Some(entries[1]) - ); - }); + let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app); + assert_eq!(active_pane.items().len(), 2); + assert_eq!( + active_pane.active_item().unwrap().entry_id(app.as_ref()), + Some(entries[1]) + ); // Open the first entry again - workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(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); - assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(app), - Some(entries[0]) - ); - }); + let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app); + assert_eq!(active_pane.items().len(), 2); + assert_eq!( + active_pane.active_item().unwrap().entry_id(app.as_ref()), + Some(entries[0]) + ); // Open the third entry twice concurrently - workspace_view.update(&mut app, |w, ctx| { + workspace_view.update(app, |w, ctx| { w.open_entry(entries[2], ctx); w.open_entry(entries[2], ctx); }); app.finish_pending_tasks().await; - workspace_view.read(&app, |w, app| { - assert_eq!(w.active_pane().as_ref(app).items().len(), 3); - }); - - Ok(()) - }) + assert_eq!( + workspace_view + .as_ref(app) + .active_pane() + .as_ref(app) + .items() + .len(), + 3 + ); + }); } #[test] - fn test_pane_actions() -> Result<()> { - App::test((), |mut app| async move { - pane::init(&mut app); + fn test_pane_actions() { + App::test_async((), |app| async move { + pane::init(app); let dir = temp_tree(json!({ "a": { @@ -474,35 +479,37 @@ mod tests { }, })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + 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 = workspace.file_entries(app); let (window_id, workspace_view) = app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); - workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(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 = workspace_view.as_ref(app).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()); + let pane_2 = workspace_view.as_ref(app).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 + .as_ref(app) + .active_item() + .unwrap() + .entry_id(app.downgrade()), + Some(entries[0]) + ); app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ()); - workspace_view.read(&app, |w, _| { - assert_eq!(w.panes.len(), 1); - assert_eq!(w.active_pane(), &pane_1) - }); - - Ok(()) - }) + let w = workspace_view.as_ref(app); + assert_eq!(w.panes.len(), 1); + assert_eq!(w.active_pane(), &pane_1); + }); } } diff --git a/zed/src/worktree/worktree.rs b/zed/src/worktree/worktree.rs index bbb264df62..27f42d904d 100644 --- a/zed/src/worktree/worktree.rs +++ b/zed/src/worktree/worktree.rs @@ -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((), |app| async move { let dir = temp_tree(json!({ "root": { "apple": "", @@ -666,33 +666,31 @@ 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, _| { - assert_eq!(tree.file_count(), 4); - let results = match_paths(&[tree.clone()], "bna", false, false, 10) - .iter() - .map(|result| tree.entry_path(result.entry_id)) - .collect::, _>>() - .unwrap(); - assert_eq!( - results, - vec![ - PathBuf::from("root_link/banana/carrot/date"), - PathBuf::from("root_link/banana/carrot/endive"), - ] - ); - }); - Ok(()) - }) + let tree = tree.as_ref(app); + assert_eq!(tree.file_count(), 4); + let results = match_paths(&[tree.clone()], "bna", false, false, 10) + .iter() + .map(|result| tree.entry_path(result.entry_id)) + .collect::, _>>() + .unwrap(); + assert_eq!( + results, + vec![ + PathBuf::from("root_link/banana/carrot/date"), + PathBuf::from("root_link/banana/carrot/endive"), + ] + ); + }); } #[test] fn test_save_file() { - App::test((), |mut app| async move { + App::test_async((), |app| async move { let dir = temp_tree(json!({ "file1": "the old contents", })); @@ -700,24 +698,18 @@ 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 entry = tree.as_ref(app).files().next().unwrap(); + assert_eq!(entry.path.file_name().unwrap(), "file1"); + let file_id = entry.entry_id; let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024)); - tree.update(&mut app, |tree, ctx| { + tree.update(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)) - .await - .unwrap(); - + let history = tree.as_ref(app).load_history(file_id).await.unwrap(); assert_eq!(history.base_text, buffer.text()); - }) + }); } } From 448dace2817a98e730e1fd0b79c5c874f7616e8b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 9 Apr 2021 21:33:17 -0600 Subject: [PATCH 10/15] Pass the on_finish_launching callback to Platform::run --- gpui/examples/text.rs | 10 +++---- gpui/src/app.rs | 37 ++++++++++------------- gpui/src/platform/mac/mod.rs | 10 ++----- gpui/src/platform/mac/platform.rs | 50 ++++++++++++++----------------- gpui/src/platform/mod.rs | 2 +- gpui/src/platform/test.rs | 4 ++- zed/src/main.rs | 5 ++-- 7 files changed, 51 insertions(+), 67 deletions(-) diff --git a/gpui/examples/text.rs b/gpui/examples/text.rs index 2dad6e0786..df1f367cdb 100644 --- a/gpui/examples/text.rs +++ b/gpui/examples/text.rs @@ -10,12 +10,10 @@ use simplelog::SimpleLogger; fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - let app = gpui::App::new(()).unwrap(); - app.on_finish_launching(|app| { - 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 ab3f471e73..48720eabcb 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -92,8 +92,8 @@ impl App { let platform = platform::test::platform(); let foreground = Rc::new(executor::Foreground::test()); let ctx = Rc::new(RefCell::new(MutableAppContext::new( - foreground.clone(), - Arc::new(platform), + foreground, + Rc::new(platform), asset_source, ))); ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx)); @@ -110,7 +110,7 @@ impl App { let foreground = Rc::new(executor::Foreground::test()); let ctx = Rc::new(RefCell::new(MutableAppContext::new( foreground.clone(), - Arc::new(platform), + Rc::new(platform), asset_source, ))); let mut ctx_ref = ctx.borrow_mut(); @@ -200,24 +200,19 @@ impl App { self } - pub fn on_finish_launching(self, callback: F) -> Self - where - F: 'static + FnOnce(&mut MutableAppContext), - { - let ctx = self.0.clone(); - self.0 - .borrow() - .platform - .on_finish_launching(Box::new(move || callback(&mut *ctx.borrow_mut()))); - self - } - pub fn set_menus(&self, menus: &[Menu]) { self.0.borrow().platform.set_menus(menus); } - pub fn run(self) { - platform::current::run(); + pub fn run(self, on_finish_launching: F) + where + F: 'static + FnOnce(&mut MutableAppContext), + { + let platform = self.platform(); + platform.run(Box::new(move || { + let mut ctx = self.0.borrow_mut(); + on_finish_launching(&mut *ctx); + })) } pub fn on_window_invalidated( @@ -354,7 +349,7 @@ impl App { self.0.borrow().font_cache.clone() } - pub fn platform(&self) -> Arc { + pub fn platform(&self) -> Rc { self.0.borrow().platform.clone() } } @@ -394,7 +389,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, @@ -422,7 +417,7 @@ pub struct MutableAppContext { impl MutableAppContext { pub fn new( foreground: Rc, - platform: Arc, + platform: Rc, asset_source: impl AssetSource, ) -> Self { let fonts = platform.fonts(); @@ -466,7 +461,7 @@ impl MutableAppContext { &self.ctx } - pub fn platform(&self) -> Arc { + pub fn platform(&self) -> Rc { self.platform.clone() } diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index 07ada3650a..c2a88c12a4 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/gpui/src/platform/mac/mod.rs @@ -12,15 +12,11 @@ use cocoa::base::{BOOL, NO, YES}; pub use dispatcher::Dispatcher; pub use fonts::FontSystem; use platform::MacPlatform; -use std::sync::Arc; +use std::rc::Rc; use window::Window; -pub fn platform() -> Arc { - MacPlatform::new() -} - -pub fn run() { - MacPlatform::run(); +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 index 40bd3566a5..4201100a6d 100644 --- a/gpui/src/platform/mac/platform.rs +++ b/gpui/src/platform/mac/platform.rs @@ -29,7 +29,7 @@ use std::{ sync::Arc, }; -const MAC_PLATFORM_IVAR: &'static str = "runner"; +const MAC_PLATFORM_IVAR: &'static str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -90,35 +90,12 @@ struct Callbacks { } impl MacPlatform { - pub fn new() -> Arc { - let result = Arc::new(Self { + pub fn new() -> Self { + Self { dispatcher: Arc::new(Dispatcher), fonts: Arc::new(FontSystem::new()), callbacks: Default::default(), menu_item_actions: Default::default(), - }); - - unsafe { - let app: id = msg_send![APP_CLASS, sharedApplication]; - let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - let self_ptr = result.as_ref() as *const Self as *const c_void; - app.setDelegate_(app_delegate); - (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); - (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); - } - - result - } - - pub fn run() { - unsafe { - let pool = NSAutoreleasePool::new(nil); - let app: id = msg_send![APP_CLASS, sharedApplication]; - - app.run(); - pool.drain(); - (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); - (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::()); } } @@ -220,8 +197,25 @@ impl platform::Platform for MacPlatform { self.callbacks.borrow_mut().open_files = Some(callback); } - fn on_finish_launching(&self, callback: Box ()>) { - self.callbacks.borrow_mut().finish_launching = 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 { diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index 6e5d53585f..39825c941e 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -28,7 +28,7 @@ pub trait Platform { fn on_resign_active(&self, callback: Box); fn on_event(&self, callback: Box bool>); fn on_open_files(&self, callback: Box)>); - fn on_finish_launching(&self, callback: Box ()>); + fn run(&self, on_finish_launching: Box ()>); fn dispatcher(&self) -> Arc; fn fonts(&self) -> Arc; diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 1cd4399c1b..b0d19232dc 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -39,7 +39,9 @@ impl super::Platform for Platform { fn on_open_files(&self, _: Box)>) {} - fn on_finish_launching(&self, _: Box ()>) {} + fn run(&self, _on_finish_launching: Box ()>) { + unimplemented!() + } fn dispatcher(&self) -> Arc { self.dispatcher.clone() diff --git a/zed/src/main.rs b/zed/src/main.rs index 6d171a5a29..b214924a80 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -35,7 +35,7 @@ fn main() { _ => ctx.dispatch_global_action(command, ()), } }) - .on_finish_launching(move |ctx| { + .run(move |ctx| { workspace::init(ctx); editor::init(ctx); file_finder::init(ctx); @@ -54,8 +54,7 @@ fn main() { }, ); } - }) - .run(); + }); } fn init_logger() { From 97a8a8ed43f975536d60d617fe7974cb833f62be Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 10 Apr 2021 00:05:09 -0600 Subject: [PATCH 11/15] Remove unsafe code from App::test_async I don't actually think it was correct to allow the future to borrow a mutable app reference. I went back to passing a wrapper around the refcell to async tests. They'll be a bit more annoying to write but also totally safe. --- gpui/src/app.rs | 210 +++++++++++-------------- zed/src/editor/buffer_element.rs | 20 +-- zed/src/editor/buffer_view.rs | 110 ++++++------- zed/src/editor/display_map/fold_map.rs | 18 +-- zed/src/editor/display_map/mod.rs | 10 +- zed/src/file_finder.rs | 40 ++--- zed/src/workspace/mod.rs | 4 +- zed/src/workspace/workspace.rs | 22 +-- zed/src/workspace/workspace_view.rs | 138 ++++++++-------- zed/src/worktree/worktree.rs | 60 ++++--- 10 files changed, 317 insertions(+), 315 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 48720eabcb..702fc952a7 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -45,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 { @@ -56,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 { @@ -84,6 +84,9 @@ pub enum MenuItem<'a> { #[derive(Clone)] pub struct App(Rc>); +#[derive(Clone)] +pub struct TestAppContext(Rc>); + impl App { pub fn test T>( asset_source: A, @@ -101,27 +104,21 @@ impl App { f(&mut *ctx) } - pub fn test_async<'a, T, F, A: AssetSource, Fn>(asset_source: A, f: Fn) -> T + pub fn test_async(asset_source: A, f: Fn) -> T where - Fn: FnOnce(&'a mut MutableAppContext) -> F, - F: Future + 'a, + Fn: FnOnce(TestAppContext) -> F, + F: Future, { let platform = platform::test::platform(); let foreground = Rc::new(executor::Foreground::test()); - let ctx = Rc::new(RefCell::new(MutableAppContext::new( + let ctx = TestAppContext(Rc::new(RefCell::new(MutableAppContext::new( foreground.clone(), Rc::new(platform), asset_source, - ))); - let mut ctx_ref = ctx.borrow_mut(); - ctx_ref.weak_self = Some(Rc::downgrade(&ctx)); - let ctx = &mut *ctx_ref; + )))); + ctx.0.borrow_mut().weak_self = Some(Rc::downgrade(&ctx.0)); - // TODO - is there a better way of getting this to compile? - let ctx = unsafe { std::mem::transmute(ctx) }; let future = f(ctx); - - drop(ctx_ref); smol::block_on(foreground.run(future)) } @@ -208,42 +205,27 @@ impl App { where F: 'static + FnOnce(&mut MutableAppContext), { - let platform = self.platform(); + 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 on_window_invalidated( - &self, - window_id: usize, - callback: F, - ) { - self.0 - .borrow_mut() - .on_window_invalidated(window_id, callback); + pub fn font_cache(&self) -> Arc { + self.0.borrow().font_cache.clone() } - pub fn add_action(&self, name: S, handler: F) - where - S: Into, - V: View, - T: Any, - F: 'static + FnMut(&mut V, &T, &mut ViewContext), - { - self.0.borrow_mut().add_action(name, handler); - } - - pub fn add_global_action(&self, name: S, handler: F) - where - S: Into, - T: 'static + Any, - F: 'static + FnMut(&T, &mut MutableAppContext), - { - self.0.borrow_mut().add_global_action(name, handler); + 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, @@ -259,10 +241,6 @@ impl App { ); } - pub fn add_bindings>(&self, bindings: T) { - self.0.borrow_mut().add_bindings(bindings); - } - pub fn dispatch_keystroke( &self, window_id: usize, @@ -329,7 +307,7 @@ impl App { handle } - pub fn read T>(&mut self, callback: F) -> T { + pub fn read T>(&self, callback: F) -> T { callback(self.0.borrow().downgrade()) } @@ -354,7 +332,7 @@ impl App { } } -impl UpdateModel for App { +impl UpdateModel for TestAppContext { fn update_model(&mut self, handle: &ModelHandle, update: F) -> S where T: Entity, @@ -368,7 +346,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, @@ -1249,8 +1227,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() @@ -1287,8 +1265,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") @@ -1387,8 +1365,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() @@ -1400,8 +1378,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() @@ -1672,9 +1650,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) } } @@ -1927,9 +1905,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) } } @@ -1943,9 +1921,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) } } @@ -1994,8 +1972,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, A: ReadModel>(&self, app: &'a A) -> &'a T { + app.read_model(self) } pub fn update(&self, app: &mut A, update: F) -> S @@ -2122,8 +2100,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, A: ReadView>(&self, app: &'a A) -> &'a T { + app.read_view(self) } pub fn update(&self, app: &mut A, update: F) -> S @@ -2470,9 +2448,9 @@ mod tests { ctx.notify(); ctx.emit(2); }); - assert_eq!(handle_1.as_ref(app).events, vec!["updated".to_string()]); + assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]); assert_eq!( - handle_2.as_ref(app).events, + handle_2.read(app).events, vec![ "observed event 1".to_string(), "notified".to_string(), @@ -2518,10 +2496,10 @@ mod tests { }); handle_2.update(app, |_, c| c.emit(7)); - assert_eq!(handle_1.as_ref(app).events, vec![7]); + assert_eq!(handle_1.read(app).events, vec![7]); handle_2.update(app, |_, c| c.emit(5)); - assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5]); + assert_eq!(handle_1.read(app).events, vec![7, 10, 5]); }) } @@ -2544,9 +2522,9 @@ mod tests { 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); }); }); }); @@ -2555,13 +2533,13 @@ mod tests { model.count = 7; c.notify() }); - assert_eq!(handle_1.as_ref(app).events, vec![7]); + assert_eq!(handle_1.read(app).events, vec![7]); handle_2.update(app, |model, c| { model.count = 5; c.notify() }); - assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5]) + assert_eq!(handle_1.read(app).events, vec![7, 10, 5]) }) } @@ -2576,25 +2554,25 @@ mod tests { type Event = (); } - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let handle = app.add_model(|_| Model::default()); handle - .update(app, |_, c| { + .update(&mut app, |_, c| { c.spawn(async { 7 }, |model, output, _| { model.count = output; }) }) .await; - assert_eq!(handle.as_ref(app).count, 7); + app.read(|ctx| assert_eq!(handle.read(ctx).count, 7)); handle - .update(app, |_, c| { + .update(&mut app, |_, c| { c.spawn(async { 14 }, |model, output, _| { model.count = output; }) }) .await; - assert_eq!(handle.as_ref(app).count, 14); + app.read(|ctx| assert_eq!(handle.read(ctx).count, 14)); }); } @@ -2609,10 +2587,10 @@ mod tests { type Event = (); } - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let handle = app.add_model(|_| Model::default()); handle - .update(app, |_, c| { + .update(&mut app, |_, c| { c.spawn_stream( smol::stream::iter(vec![1, 2, 3]), |model, output, _| { @@ -2624,7 +2602,7 @@ mod tests { ) }) .await; - assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None]) + app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None])); }) } @@ -2674,9 +2652,9 @@ mod tests { ctx.emit(1); ctx.emit(2); }); - assert_eq!(handle_1.as_ref(app).events, vec!["updated".to_string()]); + assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]); assert_eq!( - handle_2.as_ref(app).events, + handle_2.read(app).events, vec![ "observed event 1".to_string(), "observed event 2".to_string(), @@ -2742,13 +2720,13 @@ mod tests { }); handle_2.update(app, |_, c| c.emit(7)); - assert_eq!(handle_1.as_ref(app).events, vec![7]); + assert_eq!(handle_1.read(app).events, vec![7]); handle_2.update(app, |_, c| c.emit(5)); - assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5]); + assert_eq!(handle_1.read(app).events, vec![7, 10, 5]); handle_3.update(app, |_, c| c.emit(9)); - assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5, 9]); + assert_eq!(handle_1.read(app).events, vec![7, 10, 5, 9]); }) } @@ -2837,7 +2815,7 @@ mod tests { 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) }); }); @@ -2845,7 +2823,7 @@ mod tests { model.count = 11; c.notify(); }); - assert_eq!(view.as_ref(app).events, vec![11]); + assert_eq!(view.read(app).events, vec![11]); }) } @@ -2942,7 +2920,7 @@ mod tests { }); assert_eq!( - view_1.as_ref(app).events, + view_1.read(app).events, [ "self focused".to_string(), "self blurred".to_string(), @@ -2975,24 +2953,24 @@ mod tests { } } - App::test_async((), |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(app, |_, c| { + .update(&mut app, |_, c| { c.spawn(async { 7 }, |me, output, _| { me.count = output; }) }) .await; - assert_eq!(handle.as_ref(app).count, 7); + app.read(|ctx| assert_eq!(handle.read(ctx).count, 7)); handle - .update(app, |_, c| { + .update(&mut app, |_, c| { c.spawn(async { 14 }, |me, output, _| { me.count = output; }) }) .await; - assert_eq!(handle.as_ref(app).count, 14); + app.read(|ctx| assert_eq!(handle.read(ctx).count, 14)); }); } @@ -3017,10 +2995,10 @@ mod tests { } } - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let (_, handle) = app.add_window(|_| View::default()); handle - .update(app, |_, c| { + .update(&mut app, |_, c| { c.spawn_stream( smol::stream::iter(vec![1_usize, 2, 3]), |me, output, _| { @@ -3033,7 +3011,7 @@ mod tests { }) .await; - assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None]) + app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None])) }); } @@ -3335,49 +3313,49 @@ mod tests { type Event = (); } - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let model = app.add_model(|_| Model); let (_, view) = app.add_window(|_| View); - model.update(app, |_, ctx| { + model.update(&mut app, |_, ctx| { ctx.spawn(async {}, |_, _, _| {}).detach(); // Cancel this task drop(ctx.spawn(async {}, |_, _, _| {})); }); - view.update(app, |_, ctx| { + view.update(&mut app, |_, ctx| { ctx.spawn(async {}, |_, _, _| {}).detach(); // Cancel this task drop(ctx.spawn(async {}, |_, _, _| {})); }); - assert!(!app.future_handlers.borrow().is_empty()); + assert!(!app.0.borrow().future_handlers.borrow().is_empty()); app.finish_pending_tasks().await; - assert!(app.future_handlers.borrow().is_empty()); + assert!(app.0.borrow().future_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks - model.update(app, |_, ctx| { + model.update(&mut app, |_, ctx| { ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) .detach(); // Cancel this task drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); }); - view.update(app, |_, ctx| { + view.update(&mut app, |_, ctx| { ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) .detach(); // Cancel this task drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); }); - assert!(!app.stream_handlers.borrow().is_empty()); + assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; - assert!(app.stream_handlers.borrow().is_empty()); + assert!(app.0.borrow().stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks // Tasks are considered finished when we drop handles let mut tasks = Vec::new(); - model.update(app, |_, ctx| { + model.update(&mut app, |_, ctx| { tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn_stream( smol::stream::iter(vec![1, 2, 3]), @@ -3386,7 +3364,7 @@ mod tests { ))); }); - view.update(app, |_, ctx| { + view.update(&mut app, |_, ctx| { tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn_stream( smol::stream::iter(vec![1, 2, 3]), @@ -3395,12 +3373,12 @@ mod tests { ))); }); - assert!(!app.stream_handlers.borrow().is_empty()); + assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); let finish_pending_tasks = app.finish_pending_tasks(); drop(tasks); finish_pending_tasks.await; - assert!(app.stream_handlers.borrow().is_empty()); + assert!(app.0.borrow().stream_handlers.borrow().is_empty()); app.finish_pending_tasks().await; // Don't block if there are no tasks }); } diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs index e00c11f1fd..6141c0b7c6 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); @@ -404,7 +404,7 @@ impl Element for BufferElement { if let Some(layout) = layout { let app = ctx.app.downgrade(); - 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 b7974a3a2e..fadf782280 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -125,7 +125,7 @@ impl BufferView { }); ctx.observe(&display_map, Self::on_display_map_changed); - let buffer_ref = buffer.as_ref(ctx); + let buffer_ref = buffer.read(ctx); Self { handle: ctx.handle().downgrade(), buffer, @@ -188,7 +188,7 @@ impl BufferView { return false; } - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); let visible_lines = viewport_height / line_height; let first_cursor_top = self .selections @@ -238,7 +238,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; @@ -287,7 +287,7 @@ impl BufferView { ctx.emit(Event::Activate); } - let display_map = self.display_map.as_ref(ctx); + let display_map = self.display_map.read(ctx); let cursor = display_map .anchor_before(position, Bias::Left, ctx.app()) .unwrap(); @@ -312,8 +312,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); @@ -347,8 +347,8 @@ impl BufferView { where T: IntoIterator>, { - let buffer = self.buffer.as_ref(ctx); - let map = self.display_map.as_ref(ctx); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); let mut selections = Vec::new(); for range in ranges { selections.push(Selection { @@ -366,7 +366,7 @@ impl BufferView { } fn insert(&mut self, text: &String, ctx: &mut ViewContext) { - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let mut offset_ranges = SmallVec::<[Range; 32]>::new(); for selection in &self.selections { let start = selection.start.to_offset(buffer).unwrap(); @@ -381,7 +381,7 @@ impl BufferView { }; }); - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let char_count = text.chars().count() as isize; let mut delta = 0_isize; self.selections = offset_ranges @@ -416,8 +416,8 @@ impl BufferView { } pub fn backspace(&mut self, _: &(), 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); for selection in &mut self.selections { if selection.range(buffer).is_empty() { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); @@ -439,7 +439,7 @@ impl BufferView { pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext) { { let app = ctx.app(); - let map = self.display_map.as_ref(ctx); + let map = self.display_map.read(ctx); for selection in &mut self.selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -462,8 +462,8 @@ impl BufferView { pub fn select_left(&mut self, _: &(), 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); for selection in &mut self.selections { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); let cursor = map @@ -483,7 +483,7 @@ impl BufferView { pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext) { { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut self.selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -506,9 +506,9 @@ impl BufferView { pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext) { { - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut self.selections { let head = selection.head().to_display_point(map, ctx.app()).unwrap(); let cursor = map @@ -526,7 +526,7 @@ impl BufferView { ctx.propagate_action(); } else { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut self.selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -551,8 +551,8 @@ impl BufferView { ctx.propagate_action(); } else { let app = ctx.app(); - let buffer = self.buffer.as_ref(app); - let map = self.display_map.as_ref(app); + let buffer = self.buffer.read(app); + let map = self.display_map.read(app); for selection in &mut self.selections { let head = selection.head().to_display_point(map, app).unwrap(); let (head, goal_column) = @@ -569,7 +569,7 @@ impl BufferView { ctx.propagate_action(); } else { let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &mut self.selections { let start = selection.start.to_display_point(map, app).unwrap(); let end = selection.end.to_display_point(map, app).unwrap(); @@ -594,8 +594,8 @@ impl BufferView { ctx.propagate_action(); } else { let app = ctx.app(); - let buffer = self.buffer.as_ref(ctx); - let map = self.display_map.as_ref(ctx); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); for selection in &mut self.selections { let head = selection.head().to_display_point(map, app).unwrap(); let (head, goal_column) = @@ -615,7 +615,7 @@ impl BufferView { } fn merge_selections(&mut self, ctx: &AppContext) { - let buffer = self.buffer.as_ref(ctx); + let buffer = self.buffer.read(ctx); let mut i = 1; while i < self.selections.len() { if self.selections[i - 1] @@ -651,14 +651,14 @@ impl BufferView { self.selections .first() .unwrap() - .display_range(self.display_map.as_ref(app), app) + .display_range(self.display_map.read(app), app) } pub fn last_selection(&self, app: &AppContext) -> Range { self.selections .last() .unwrap() - .display_range(self.display_map.as_ref(app), app) + .display_range(self.display_map.read(app), app) } pub fn selections_in_range<'a>( @@ -666,7 +666,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); @@ -686,7 +686,7 @@ impl BufferView { } fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize { - let buffer = self.buffer.as_ref(app); + let buffer = self.buffer.read(app); match self .selections @@ -720,7 +720,7 @@ impl BufferView { let mut fold_ranges = Vec::new(); let app = ctx.app(); - let map = self.display_map.as_ref(app); + let map = self.display_map.read(app); for selection in &self.selections { let (start, end) = selection.display_range(map, app).sorted(); let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row; @@ -750,8 +750,8 @@ impl BufferView { use super::RangeExt; let app = ctx.app(); - let map = self.display_map.as_ref(app); - let buffer = self.buffer.as_ref(app); + let map = self.display_map.read(app); + let buffer = self.buffer.read(app); let ranges = self .selections .iter() @@ -796,7 +796,7 @@ impl BufferView { let mut is_blank = true; for c in self .display_map - .as_ref(app) + .read(app) .chars_at(DisplayPoint::new(display_row, 0), app)? { if c == ' ' { @@ -810,7 +810,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)?; @@ -831,7 +831,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 .iter() @@ -842,23 +842,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 { @@ -902,7 +902,7 @@ impl BufferView { let font_size = settings.buffer_font_size; let font_id = font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; - let digit_count = ((self.buffer.as_ref(app).max_point().row + 1) as f32) + let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32) .log10() .floor() as usize + 1; @@ -923,7 +923,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; @@ -959,7 +959,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 { @@ -1149,7 +1149,7 @@ impl workspace::ItemView for BufferView { } fn title(&self, app: &AppContext) -> std::string::String { - if let Some(path) = self.buffer.as_ref(app).path(app) { + if let Some(path) = self.buffer.read(app).path(app) { path.file_name() .expect("buffer's path is always to a file") .to_string_lossy() @@ -1160,7 +1160,7 @@ impl workspace::ItemView for BufferView { } fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> { - self.buffer.as_ref(app).entry_id() + self.buffer.read(app).entry_id() } fn clone_on_split(&self, ctx: &mut ViewContext) -> Option @@ -1177,7 +1177,7 @@ impl workspace::ItemView for BufferView { } fn is_dirty(&self, ctx: &AppContext) -> bool { - self.buffer.as_ref(ctx).is_dirty() + self.buffer.read(ctx).is_dirty() } } @@ -1255,7 +1255,7 @@ mod tests { view.begin_selection(DisplayPoint::new(2, 2), false, ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1271,7 +1271,7 @@ mod tests { view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1287,7 +1287,7 @@ mod tests { view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1304,7 +1304,7 @@ mod tests { view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1321,7 +1321,7 @@ mod tests { view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1340,7 +1340,7 @@ mod tests { view.end_selection(ctx); }); - let view = buffer_view.as_ref(app); + let view = buffer_view.read(app); let selections = view .selections_in_range( DisplayPoint::zero()..view.max_point(app.as_ref()), @@ -1367,7 +1367,7 @@ mod tests { app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); let layouts = view - .as_ref(app) + .read(app) .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref()) .unwrap(); assert_eq!(layouts.len(), 6); @@ -1460,7 +1460,7 @@ mod tests { ); view.unfold(&(), ctx); - assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text()); + assert_eq!(view.text(ctx.app()), buffer.read(ctx).text()); }); }); } @@ -1529,7 +1529,7 @@ mod tests { }); assert_eq!( - buffer.as_ref(app).text(), + 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 d67b4b510e..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(); @@ -597,7 +597,7 @@ mod tests { let mut map = FoldMap::new(buffer.clone(), app.as_ref()); { - 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(); @@ -632,7 +632,7 @@ mod tests { map.apply_edits(&edits, app.as_ref()).unwrap(); - let buffer = map.buffer.as_ref(app); + 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; @@ -694,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 7bc17627dd..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) } } @@ -314,7 +314,7 @@ mod tests { }) .unwrap(); - let map = map.as_ref(app); + let map = map.read(app); assert_eq!( map.chars_at(DisplayPoint::new(1, 0), app.as_ref()) .unwrap() @@ -368,7 +368,7 @@ mod tests { let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb")); let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx)); assert_eq!( - map.as_ref(app).max_point(app.as_ref()), + 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 d03ce43294..169d2243a4 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -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() } } @@ -400,7 +400,7 @@ mod tests { #[test] fn test_matching_paths() { - App::test_async((), |app| async move { + 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") @@ -409,8 +409,10 @@ mod tests { fs::write(tmp_dir.path().join("a/bandana"), "bandana") .await .unwrap(); - super::init(app); - editor::init(app); + 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)); @@ -424,14 +426,16 @@ mod tests { (), ); - let finder = workspace_view - .as_ref(app) - .modal() - .cloned() - .unwrap() - .downcast::() - .unwrap(); - let query_buffer = finder.as_ref(app).query_buffer.clone(); + let finder = app.read(|ctx| { + workspace_view + .read(ctx) + .modal() + .cloned() + .unwrap() + .downcast::() + .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()); diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index 91dd99419a..a441eb8b8b 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -109,9 +109,9 @@ mod tests { .unwrap(); assert_eq!( workspace_view_1 - .as_ref(app) + .read(app) .workspace - .as_ref(app) + .read(app) .worktrees() .len(), 2 diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index cc5cfec602..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,18 +200,18 @@ impl Entity for Workspace { #[cfg(test)] pub trait WorkspaceHandle { - fn file_entries(&self, app: &mut MutableAppContext) -> Vec<(usize, usize)>; + fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)>; } #[cfg(test)] impl WorkspaceHandle for ModelHandle { - fn file_entries(&self, app: &mut MutableAppContext) -> Vec<(usize, usize)> { - self.as_ref(app) + fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)> { + self.read(app) .worktrees() .iter() .flat_map(|tree| { let tree_id = tree.id(); - tree.as_ref(app) + tree.read(app) .files() .map(move |file| (tree_id, file.entry_id)) }) @@ -228,7 +228,7 @@ mod tests { #[test] fn test_open_entry() { - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "a": { "aa": "aa contents", @@ -240,12 +240,12 @@ mod tests { app.finish_pending_tasks().await; // Open and populate worktree. // Get the first file entry. - let tree = workspace.as_ref(app).worktrees.iter().next().unwrap(); - let entry_id = tree.as_ref(app).files().next().unwrap().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(app, |w, app| { + let (future_1, future_2) = workspace.update(&mut app, |w, app| { ( w.open_entry(entry, app).unwrap(), w.open_entry(entry, app).unwrap(), @@ -258,7 +258,7 @@ mod tests { // Open the same entry again now that it has loaded let handle_3 = workspace - .update(app, |w, app| w.open_entry(entry, app).unwrap()) + .update(&mut app, |w, app| w.open_entry(entry, app).unwrap()) .await .unwrap(); diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index aaf1e9eb9a..aece490d37 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -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); } @@ -394,7 +394,7 @@ mod tests { #[test] fn test_open_entry() { - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "a": { "aa": "aa contents", @@ -406,70 +406,78 @@ 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)); // Open the first entry - workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); app.finish_pending_tasks().await; - assert_eq!( - workspace_view - .as_ref(app) - .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(app, |w, ctx| w.open_entry(entries[1], ctx)); + workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx)); app.finish_pending_tasks().await; - let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app); - assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(app.as_ref()), - Some(entries[1]) - ); + 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(ctx), + Some(entries[1]) + ); + }); // Open the first entry again - workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); app.finish_pending_tasks().await; - let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app); - assert_eq!(active_pane.items().len(), 2); - assert_eq!( - active_pane.active_item().unwrap().entry_id(app.as_ref()), - Some(entries[0]) - ); + 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(ctx), + Some(entries[0]) + ); + }); // Open the third entry twice concurrently - workspace_view.update(app, |w, ctx| { + workspace_view.update(&mut app, |w, ctx| { w.open_entry(entries[2], ctx); w.open_entry(entries[2], ctx); }); app.finish_pending_tasks().await; - assert_eq!( - workspace_view - .as_ref(app) - .active_pane() - .as_ref(app) - .items() - .len(), - 3 - ); + app.read(|ctx| { + assert_eq!( + workspace_view + .read(ctx) + .active_pane() + .read(ctx) + .items() + .len(), + 3 + ); + }); }); } #[test] fn test_pane_actions() { - App::test_async((), |app| async move { - pane::init(app); + App::test_async((), |mut app| async move { + app.update(|ctx| pane::init(ctx)); let dir = temp_tree(json!({ "a": { @@ -479,37 +487,41 @@ mod tests { }, })); - let settings = settings::channel(app.font_cache()).unwrap().1; + 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)); - workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx)); + workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); app.finish_pending_tasks().await; - let pane_1 = workspace_view.as_ref(app).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.as_ref(app).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); - assert_eq!( - pane_2 - .as_ref(app) - .active_item() - .unwrap() - .entry_id(app.downgrade()), - 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", ()); + }); - let w = workspace_view.as_ref(app); - assert_eq!(w.panes.len(), 1); - assert_eq!(w.active_pane(), &pane_1); + app.read(|ctx| { + let w = workspace_view.read(ctx); + assert_eq!(w.panes.len(), 1); + assert_eq!(w.active_pane(), &pane_1); + }) }); } } diff --git a/zed/src/worktree/worktree.rs b/zed/src/worktree/worktree.rs index 27f42d904d..63be9b6eef 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) } @@ -649,7 +649,7 @@ mod test { #[test] fn test_populate_and_search() { - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "root": { "apple": "", @@ -671,26 +671,28 @@ mod test { let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx))); app.finish_pending_tasks().await; - let tree = tree.as_ref(app); - assert_eq!(tree.file_count(), 4); - let results = match_paths(&[tree.clone()], "bna", false, false, 10) - .iter() - .map(|result| tree.entry_path(result.entry_id)) - .collect::, _>>() - .unwrap(); - assert_eq!( - results, - vec![ - PathBuf::from("root_link/banana/carrot/date"), - PathBuf::from("root_link/banana/carrot/endive"), - ] - ); + 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() + .map(|result| tree.entry_path(result.entry_id)) + .collect::, _>>() + .unwrap(); + assert_eq!( + results, + vec![ + PathBuf::from("root_link/banana/carrot/date"), + PathBuf::from("root_link/banana/carrot/endive"), + ] + ); + }) }); } #[test] fn test_save_file() { - App::test_async((), |app| async move { + App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "file1": "the old contents", })); @@ -698,17 +700,23 @@ mod test { let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx))); app.finish_pending_tasks().await; - let entry = tree.as_ref(app).files().next().unwrap(); - assert_eq!(entry.path.file_name().unwrap(), "file1"); - let file_id = entry.entry_id; - let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024)); - tree.update(app, |tree, ctx| { + 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.as_ref(app).load_history(file_id).await.unwrap(); + let history = app + .read(|ctx| tree.read(ctx).load_history(file_id)) + .await + .unwrap(); assert_eq!(history.base_text, buffer.text()); }); } From 620eedb727cb383e1385c4e8972da6190c4525c8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 10 Apr 2021 00:11:13 -0600 Subject: [PATCH 12/15] Allow effects to be flushed before TestAppContext::update callback completes --- gpui/src/app.rs | 7 +++++-- zed/src/workspace/workspace_view.rs | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 702fc952a7..413c838fda 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -313,8 +313,11 @@ impl TestAppContext { 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 } @@ -895,7 +898,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; diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index aece490d37..6a9b3ca320 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -515,13 +515,11 @@ mod tests { ); ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ()); - }); - app.read(|ctx| { let w = workspace_view.read(ctx); assert_eq!(w.panes.len(), 1); assert_eq!(w.active_pane(), &pane_1); - }) + }); }); } } From 4638391412b7ba6ccaf72663f8829f65d7084f51 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 10 Apr 2021 00:14:26 -0600 Subject: [PATCH 13/15] Remove MutableAppContext::downgrade --- gpui/src/app.rs | 21 ++++++++------------- gpui/src/presenter.rs | 4 ++-- zed/src/editor/buffer_element.rs | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 413c838fda..e5f40c6ba5 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -308,7 +308,7 @@ impl TestAppContext { } pub fn read T>(&self, callback: F) -> T { - callback(self.0.borrow().downgrade()) + callback(self.0.borrow().as_ref()) } pub fn update T>(&mut self, callback: F) -> T { @@ -438,10 +438,6 @@ 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() } @@ -679,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!( @@ -772,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() @@ -781,9 +777,8 @@ 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_any( window_id, @@ -815,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); @@ -1771,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); @@ -1797,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); 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_element.rs b/zed/src/editor/buffer_element.rs index 6141c0b7c6..eabd891791 100644 --- a/zed/src/editor/buffer_element.rs +++ b/zed/src/editor/buffer_element.rs @@ -402,7 +402,7 @@ 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.read(app); view.clamp_scroll_left( From 619e2b7e018ceab1acee8849d17a727726bf144f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 10 Apr 2021 00:19:25 -0600 Subject: [PATCH 14/15] Remove platform::mac::runner --- gpui/src/platform/mac/runner.rs | 305 -------------------------------- 1 file changed, 305 deletions(-) delete mode 100644 gpui/src/platform/mac/runner.rs diff --git a/gpui/src/platform/mac/runner.rs b/gpui/src/platform/mac/runner.rs deleted file mode 100644 index 2c2c3ecab4..0000000000 --- a/gpui/src/platform/mac/runner.rs +++ /dev/null @@ -1,305 +0,0 @@ -use crate::{keymap::Keystroke, platform::Event, Menu, MenuItem}; -use cocoa::{ - appkit::{ - NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - NSEventModifierFlags, NSMenu, NSMenuItem, NSWindow, - }, - base::{id, nil, selector}, - foundation::{NSArray, NSAutoreleasePool, NSInteger, 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!(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() - } -} - -#[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)>>, - menu_command_callback: Option>, - menu_item_actions: Vec, -} - -impl Runner { - pub fn new() -> Self { - Default::default() - } - - unsafe fn create_menu_bar(&mut self, menus: &[Menu]) -> id { - let menu_bar = NSMenu::new(nil).autorelease(); - self.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 = self.menu_item_actions.len() as NSInteger; - let _: () = msg_send![item, setTag: tag]; - self.menu_item_actions.push(action.to_string()); - } - } - - menu.addItem_(item); - } - - menu_bar_item.setSubmenu_(menu); - menu_bar.addItem_(menu_bar_item); - } - - menu_bar - } -} - -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_menu_command(mut self, callback: F) -> Self { - self.menu_command_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 set_menus(mut self, menus: &[Menu]) -> Self { - unsafe { - let app: id = msg_send![APP_CLASS, sharedApplication]; - app.setMainMenu_(self.create_menu_bar(menus)); - } - 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 app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; - - (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void); - app.setDelegate_(app_delegate); - app.run(); - pool.drain(); - - // 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) { - unsafe { - let app: id = msg_send![APP_CLASS, sharedApplication]; - app.setActivationPolicy_(NSApplicationActivationPolicyRegular); - - let runner = get_runner(this); - if let Some(callback) = runner.finish_launching_callback.take() { - 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); - } -} - -extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { - unsafe { - let runner = get_runner(this); - if let Some(callback) = runner.menu_command_callback.as_mut() { - let tag: NSInteger = msg_send![item, tag]; - let index = tag as usize; - if let Some(action) = runner.menu_item_actions.get(index) { - callback(&action); - } - } - } -} - -unsafe fn ns_string(string: &str) -> id { - NSString::alloc(nil).init_str(string).autorelease() -} From b9b511148bfa6174231decee440d50504831270c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 10 Apr 2021 00:22:45 -0600 Subject: [PATCH 15/15] Make gpui::platform module private --- gpui/src/lib.rs | 4 ++-- gpui/src/platform/test.rs | 2 -- zed/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) 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/test.rs b/gpui/src/platform/test.rs index b0d19232dc..f1d6bead66 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -17,8 +17,6 @@ pub struct Window { resize_handlers: Vec>, } -pub struct WindowContext {} - impl Platform { fn new() -> Self { Self { diff --git a/zed/src/main.rs b/zed/src/main.rs index b214924a80..407b4952c1 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -1,5 +1,5 @@ use fs::OpenOptions; -use gpui::platform::PathPromptOptions; +use gpui::PathPromptOptions; use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf};