Detailed changes
@@ -399,8 +399,7 @@ dependencies = [
[[package]]
name = "cocoa"
version = "0.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
dependencies = [
"bitflags",
"block",
@@ -415,8 +414,7 @@ dependencies = [
[[package]]
name = "cocoa-foundation"
version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
dependencies = [
"bitflags",
"block",
@@ -445,8 +443,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
dependencies = [
"core-foundation-sys",
"libc",
@@ -455,14 +452,12 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
[[package]]
name = "core-graphics"
version = "0.22.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
dependencies = [
"bitflags",
"core-foundation",
@@ -474,8 +469,7 @@ dependencies = [
[[package]]
name = "core-graphics-types"
version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
+source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
dependencies = [
"bitflags",
"core-foundation",
@@ -3,3 +3,9 @@ members = ["zed", "gpui"]
[patch.crates-io]
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
+
+# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/454
+cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
+cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
+core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
+core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
@@ -1,7 +1,6 @@
use gpui::{
color::ColorU,
fonts::{Properties, Weight},
- platform::{current as platform, Runner},
DebugContext, Element as _, Quad,
};
use log::LevelFilter;
@@ -11,13 +10,10 @@ use simplelog::SimpleLogger;
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
- let mut app = gpui::App::new(()).unwrap();
- platform::runner()
- .on_finish_launching(move || {
- app.platform().activate(true);
- app.add_window(|_| TextView);
- })
- .run();
+ gpui::App::new(()).unwrap().run(|ctx| {
+ ctx.platform().activate(true);
+ ctx.add_window(|_| TextView);
+ });
}
struct TextView;
@@ -2,7 +2,7 @@ use crate::{
elements::ElementBox,
executor,
keymap::{self, Keystroke},
- platform::{self, App as _, WindowOptions},
+ platform::{self, WindowOptions},
presenter::Presenter,
util::post_inc,
AssetCache, AssetSource, FontCache, TextLayoutCache,
@@ -21,6 +21,7 @@ use std::{
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
+ path::PathBuf,
rc::{self, Rc},
sync::{Arc, Weak},
};
@@ -44,8 +45,8 @@ pub trait View: Entity {
}
}
-pub trait ModelAsRef {
- fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
+pub trait ReadModel {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
}
pub trait UpdateModel {
@@ -55,8 +56,8 @@ pub trait UpdateModel {
F: FnOnce(&mut T, &mut ModelContext<T>) -> S;
}
-pub trait ViewAsRef {
- fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T;
+pub trait ReadView {
+ fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T;
}
pub trait UpdateView {
@@ -66,27 +67,63 @@ pub trait UpdateView {
F: FnOnce(&mut T, &mut ViewContext<T>) -> 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<RefCell<MutableAppContext>>);
+#[derive(Clone)]
+pub struct TestAppContext(Rc<RefCell<MutableAppContext>>);
+
impl App {
- pub fn test<T, A: AssetSource, F: Future<Output = T>, G: FnOnce(App) -> F>(
+ pub fn test<T, A: AssetSource, F: FnOnce(&mut MutableAppContext) -> T>(
asset_source: A,
- f: G,
+ f: F,
) -> T {
- let platform = platform::test::app();
+ let platform = platform::test::platform();
let foreground = Rc::new(executor::Foreground::test());
- let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
+ let ctx = Rc::new(RefCell::new(MutableAppContext::new(
+ foreground,
+ Rc::new(platform),
+ asset_source,
+ )));
+ ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx));
+ let mut ctx = ctx.borrow_mut();
+ f(&mut *ctx)
+ }
+
+ pub fn test_async<T, F, A: AssetSource, Fn>(asset_source: A, f: Fn) -> T
+ where
+ Fn: FnOnce(TestAppContext) -> F,
+ F: Future<Output = T>,
+ {
+ let platform = platform::test::platform();
+ let foreground = Rc::new(executor::Foreground::test());
+ let ctx = TestAppContext(Rc::new(RefCell::new(MutableAppContext::new(
foreground.clone(),
- Arc::new(platform),
+ Rc::new(platform),
asset_source,
))));
- app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
- smol::block_on(foreground.run(f(app)))
+ ctx.0.borrow_mut().weak_self = Some(Rc::downgrade(&ctx.0));
+
+ let future = f(ctx);
+ smol::block_on(foreground.run(future))
}
pub fn new(asset_source: impl AssetSource) -> Result<Self> {
- let platform = Arc::new(platform::current::app());
+ let platform = platform::current::platform();
let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
foreground,
@@ -97,35 +134,98 @@ impl App {
Ok(app)
}
- pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>(
- &self,
- window_id: usize,
- callback: F,
- ) {
+ pub fn on_become_active<F>(self, mut callback: F) -> Self
+ where
+ F: 'static + FnMut(&mut MutableAppContext),
+ {
+ let ctx = self.0.clone();
self.0
- .borrow_mut()
- .on_window_invalidated(window_id, callback);
+ .borrow()
+ .platform
+ .on_become_active(Box::new(move || callback(&mut *ctx.borrow_mut())));
+ self
}
- pub fn add_action<S, V, T, F>(&self, name: S, handler: F)
+ pub fn on_resign_active<F>(self, mut callback: F) -> Self
where
- S: Into<String>,
- V: View,
- T: Any,
- F: 'static + FnMut(&mut V, &T, &mut ViewContext<V>),
+ F: 'static + FnMut(&mut MutableAppContext),
{
- self.0.borrow_mut().add_action(name, handler);
+ let ctx = self.0.clone();
+ self.0
+ .borrow()
+ .platform
+ .on_resign_active(Box::new(move || callback(&mut *ctx.borrow_mut())));
+ self
}
- pub fn add_global_action<S, T, F>(&self, name: S, handler: F)
+ pub fn on_event<F>(self, mut callback: F) -> Self
where
- S: Into<String>,
- T: 'static + Any,
- F: 'static + FnMut(&T, &mut MutableAppContext),
+ F: 'static + FnMut(Event, &mut MutableAppContext) -> bool,
{
- self.0.borrow_mut().add_global_action(name, handler);
+ let ctx = self.0.clone();
+ self.0.borrow().platform.on_event(Box::new(move |event| {
+ callback(event, &mut *ctx.borrow_mut())
+ }));
+ self
+ }
+
+ pub fn on_menu_command<F>(self, mut callback: F) -> Self
+ where
+ F: 'static + FnMut(&str, &mut MutableAppContext),
+ {
+ let ctx = self.0.clone();
+ self.0
+ .borrow()
+ .platform
+ .on_menu_command(Box::new(move |command| {
+ callback(command, &mut *ctx.borrow_mut())
+ }));
+ self
+ }
+
+ pub fn on_open_files<F>(self, mut callback: F) -> Self
+ where
+ F: 'static + FnMut(Vec<PathBuf>, &mut MutableAppContext),
+ {
+ let ctx = self.0.clone();
+ self.0
+ .borrow()
+ .platform
+ .on_open_files(Box::new(move |paths| {
+ callback(paths, &mut *ctx.borrow_mut())
+ }));
+ self
+ }
+
+ pub fn set_menus(&self, menus: &[Menu]) {
+ self.0.borrow().platform.set_menus(menus);
}
+ pub fn run<F>(self, on_finish_launching: F)
+ where
+ F: 'static + FnOnce(&mut MutableAppContext),
+ {
+ let platform = self.0.borrow().platform.clone();
+ platform.run(Box::new(move || {
+ let mut ctx = self.0.borrow_mut();
+ on_finish_launching(&mut *ctx);
+ }))
+ }
+
+ pub fn font_cache(&self) -> Arc<FontCache> {
+ self.0.borrow().font_cache.clone()
+ }
+
+ fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
+ let mut state = self.0.borrow_mut();
+ state.pending_flushes += 1;
+ let result = callback(&mut *state);
+ state.flush_effects();
+ result
+ }
+}
+
+impl TestAppContext {
pub fn dispatch_action<T: 'static + Any>(
&self,
window_id: usize,
@@ -133,7 +233,7 @@ impl App {
name: &str,
arg: T,
) {
- self.0.borrow_mut().dispatch_action(
+ self.0.borrow_mut().dispatch_action_any(
window_id,
&responder_chain,
name,
@@ -141,16 +241,6 @@ impl App {
);
}
- pub fn dispatch_global_action<T: 'static + Any>(&self, name: &str, arg: T) {
- self.0
- .borrow_mut()
- .dispatch_global_action(name, Box::new(arg).as_ref());
- }
-
- pub fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&self, bindings: T) {
- self.0.borrow_mut().add_bindings(bindings);
- }
-
pub fn dispatch_keystroke(
&self,
window_id: usize,
@@ -173,15 +263,6 @@ impl App {
handle
}
- fn read_model<T, F, S>(&self, handle: &ModelHandle<T>, read: F) -> S
- where
- T: Entity,
- F: FnOnce(&T, &AppContext) -> S,
- {
- let state = self.0.borrow();
- read(state.model(handle), &state.ctx)
- }
-
pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
where
T: View,
@@ -226,27 +307,21 @@ impl App {
handle
}
- pub fn read<T, F: FnOnce(&AppContext) -> T>(&mut self, callback: F) -> T {
- callback(self.0.borrow().downgrade())
+ pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
+ callback(self.0.borrow().as_ref())
}
pub fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
let mut state = self.0.borrow_mut();
- state.pending_flushes += 1;
+ // Don't increment pending flushes in order to effects to be flushed before the callback
+ // completes, which is helpful in tests.
let result = callback(&mut *state);
+ // Flush effects after the callback just in case there are any. This can happen in edge
+ // cases such as the closure dropping handles.
state.flush_effects();
result
}
- fn read_view<T, F, S>(&self, handle: &ViewHandle<T>, read: F) -> S
- where
- T: View,
- F: FnOnce(&T, &AppContext) -> S,
- {
- let state = self.0.borrow();
- read(state.view(handle), state.downgrade())
- }
-
pub fn finish_pending_tasks(&self) -> impl Future<Output = ()> {
self.0.borrow().finish_pending_tasks()
}
@@ -255,12 +330,12 @@ impl App {
self.0.borrow().font_cache.clone()
}
- pub fn platform(&self) -> Arc<dyn platform::App> {
+ pub fn platform(&self) -> Rc<dyn platform::Platform> {
self.0.borrow().platform.clone()
}
}
-impl UpdateModel for App {
+impl UpdateModel for TestAppContext {
fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
where
T: Entity,
@@ -274,7 +349,7 @@ impl UpdateModel for App {
}
}
-impl UpdateView for App {
+impl UpdateView for TestAppContext {
fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
where
T: View,
@@ -295,7 +370,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext);
pub struct MutableAppContext {
weak_self: Option<rc::Weak<RefCell<Self>>>,
- platform: Arc<dyn platform::App>,
+ platform: Rc<dyn platform::Platform>,
font_cache: Arc<FontCache>,
assets: Arc<AssetCache>,
ctx: AppContext,
@@ -323,7 +398,7 @@ pub struct MutableAppContext {
impl MutableAppContext {
pub fn new(
foreground: Rc<executor::Foreground>,
- platform: Arc<dyn platform::App>,
+ platform: Rc<dyn platform::Platform>,
asset_source: impl AssetSource,
) -> Self {
let fonts = platform.fonts();
@@ -363,8 +438,12 @@ impl MutableAppContext {
App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
}
- pub fn downgrade(&self) -> &AppContext {
- &self.ctx
+ pub fn platform(&self) -> Rc<dyn platform::Platform> {
+ self.platform.clone()
+ }
+
+ pub fn font_cache(&self) -> &Arc<FontCache> {
+ &self.font_cache
}
pub fn foreground_executor(&self) -> &Rc<executor::Foreground> {
@@ -487,7 +566,24 @@ impl MutableAppContext {
self.ctx.render_views(window_id)
}
- pub fn dispatch_action(
+ pub fn update<T, F: FnOnce() -> T>(&mut self, callback: F) -> T {
+ self.pending_flushes += 1;
+ let result = callback();
+ self.flush_effects();
+ result
+ }
+
+ pub fn dispatch_action<T: 'static + Any>(
+ &mut self,
+ window_id: usize,
+ responder_chain: Vec<usize>,
+ name: &str,
+ arg: T,
+ ) {
+ self.dispatch_action_any(window_id, &responder_chain, name, Box::new(arg).as_ref());
+ }
+
+ fn dispatch_action_any(
&mut self,
window_id: usize,
path: &[usize],
@@ -538,14 +634,18 @@ impl MutableAppContext {
}
if !halted_dispatch {
- self.dispatch_global_action(name, arg);
+ self.dispatch_global_action_with_dyn_arg(name, arg);
}
self.flush_effects();
halted_dispatch
}
- fn dispatch_global_action(&mut self, name: &str, arg: &dyn Any) {
+ pub fn dispatch_global_action<T: 'static + Any>(&mut self, name: &str, arg: T) {
+ self.dispatch_global_action_with_dyn_arg(name, Box::new(arg).as_ref());
+ }
+
+ fn dispatch_global_action_with_dyn_arg(&mut self, name: &str, arg: &dyn Any) {
if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) {
self.pending_flushes += 1;
for handler in handlers.iter_mut().rev() {
@@ -556,7 +656,7 @@ impl MutableAppContext {
}
}
- fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&mut self, bindings: T) {
+ pub fn add_bindings<T: IntoIterator<Item = keymap::Binding>>(&mut self, bindings: T) {
self.keystroke_matcher.add_bindings(bindings);
}
@@ -575,7 +675,7 @@ impl MutableAppContext {
.get(&window_id)
.and_then(|w| w.views.get(view_id))
{
- context.extend(view.keymap_context(self.downgrade()));
+ context.extend(view.keymap_context(self.as_ref()));
context_chain.push(context.clone());
} else {
return Err(anyhow!(
@@ -594,7 +694,7 @@ impl MutableAppContext {
MatchResult::None => {}
MatchResult::Pending => pending = true,
MatchResult::Action { name, arg } => {
- if self.dispatch_action(
+ if self.dispatch_action_any(
window_id,
&responder_chain[0..=i],
&name,
@@ -668,7 +768,7 @@ impl MutableAppContext {
if ctx
.dispatch_keystroke(
window_id,
- presenter.borrow().dispatch_path(ctx.downgrade()),
+ presenter.borrow().dispatch_path(ctx.as_ref()),
keystroke,
)
.unwrap()
@@ -677,11 +777,10 @@ impl MutableAppContext {
}
}
- let actions = presenter
- .borrow_mut()
- .dispatch_event(event, ctx.downgrade());
+ let actions =
+ presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
for action in actions {
- ctx.dispatch_action(
+ ctx.dispatch_action_any(
window_id,
&action.path,
action.name,
@@ -711,7 +810,7 @@ impl MutableAppContext {
let presenter = presenter.clone();
self.on_window_invalidated(window_id, move |invalidation, ctx| {
let mut presenter = presenter.borrow_mut();
- presenter.invalidate(invalidation, ctx.downgrade());
+ presenter.invalidate(invalidation, ctx.as_ref());
let scene =
presenter.build_scene(window.size(), window.scale_factor(), ctx);
window.present_scene(scene);
@@ -794,7 +893,7 @@ impl MutableAppContext {
}
fn flush_effects(&mut self) {
- self.pending_flushes -= 1;
+ self.pending_flushes = self.pending_flushes.saturating_sub(1);
if !self.flushing_effects && self.pending_flushes == 0 {
self.flushing_effects = true;
@@ -1126,8 +1225,8 @@ impl MutableAppContext {
}
}
-impl ModelAsRef for MutableAppContext {
- fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+impl ReadModel for MutableAppContext {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
if let Some(model) = self.ctx.models.get(&handle.model_id) {
model
.as_any()
@@ -1164,8 +1263,8 @@ impl UpdateModel for MutableAppContext {
}
}
-impl ViewAsRef for MutableAppContext {
- fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+impl ReadView for MutableAppContext {
+ fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
if let Some(window) = self.ctx.windows.get(&handle.window_id) {
if let Some(view) = window.views.get(&handle.view_id) {
view.as_any().downcast_ref().expect("Downcast is type safe")
@@ -1213,6 +1312,12 @@ impl UpdateView for MutableAppContext {
}
}
+impl AsRef<AppContext> for MutableAppContext {
+ fn as_ref(&self) -> &AppContext {
+ &self.ctx
+ }
+}
+
pub struct AppContext {
models: HashMap<usize, Box<dyn AnyModel>>,
windows: HashMap<usize, Window>,
@@ -1258,8 +1363,8 @@ impl AppContext {
}
}
-impl ModelAsRef for AppContext {
- fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+impl ReadModel for AppContext {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
if let Some(model) = self.models.get(&handle.model_id) {
model
.as_any()
@@ -1271,8 +1376,8 @@ impl ModelAsRef for AppContext {
}
}
-impl ViewAsRef for AppContext {
- fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+impl ReadView for AppContext {
+ fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
if let Some(window) = self.windows.get(&handle.window_id) {
if let Some(view) = window.views.get(&handle.view_id) {
view.as_any()
@@ -1543,9 +1648,9 @@ impl<'a, T: Entity> ModelContext<'a, T> {
}
}
-impl<M> ModelAsRef for ModelContext<'_, M> {
- fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
- self.app.model(handle)
+impl<M> ReadModel for ModelContext<'_, M> {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+ self.app.read_model(handle)
}
}
@@ -1661,7 +1766,7 @@ impl<'a, T: View> ViewContext<'a, T> {
window_id: self.window_id,
view_id: self.view_id,
callback: Box::new(move |view, payload, app, window_id, view_id| {
- if let Some(emitter_handle) = emitter_handle.upgrade(app.downgrade()) {
+ if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) {
let model = view.downcast_mut().expect("downcast is type safe");
let payload = payload.downcast_ref().expect("downcast is type safe");
let mut ctx = ViewContext::new(app, window_id, view_id);
@@ -1687,7 +1792,7 @@ impl<'a, T: View> ViewContext<'a, T> {
window_id: self.window_id,
view_id: self.view_id,
callback: Box::new(move |view, payload, app, window_id, view_id| {
- if let Some(emitter_handle) = emitter_handle.upgrade(app.downgrade()) {
+ if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) {
let model = view.downcast_mut().expect("downcast is type safe");
let payload = payload.downcast_ref().expect("downcast is type safe");
let mut ctx = ViewContext::new(app, window_id, view_id);
@@ -1798,9 +1903,9 @@ impl<'a, T: View> ViewContext<'a, T> {
}
}
-impl<V> ModelAsRef for ViewContext<'_, V> {
- fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
- self.app.model(handle)
+impl<V> ReadModel for ViewContext<'_, V> {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+ self.app.read_model(handle)
}
}
@@ -1814,9 +1919,9 @@ impl<V: View> UpdateModel for ViewContext<'_, V> {
}
}
-impl<V: View> ViewAsRef for ViewContext<'_, V> {
- fn view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
- self.app.view(handle)
+impl<V: View> ReadView for ViewContext<'_, V> {
+ fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
+ self.app.read_view(handle)
}
}
@@ -1865,15 +1970,8 @@ impl<T: Entity> ModelHandle<T> {
self.model_id
}
- pub fn as_ref<'a, A: ModelAsRef>(&self, app: &'a A) -> &'a T {
- app.model(self)
- }
-
- pub fn read<'a, S, F>(&self, app: &'a App, read: F) -> S
- where
- F: FnOnce(&T, &AppContext) -> S,
- {
- app.read_model(self, read)
+ pub fn read<'a, A: ReadModel>(&self, app: &'a A) -> &'a T {
+ app.read_model(self)
}
pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
@@ -2000,15 +2098,8 @@ impl<T: View> ViewHandle<T> {
self.view_id
}
- pub fn as_ref<'a, A: ViewAsRef>(&self, app: &'a A) -> &'a T {
- app.view(self)
- }
-
- pub fn read<'a, F, S>(&self, app: &'a App, read: F) -> S
- where
- F: FnOnce(&T, &AppContext) -> S,
- {
- app.read_view(self, read)
+ pub fn read<'a, A: ReadView>(&self, app: &'a A) -> &'a T {
+ app.read_view(self)
}
pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
@@ -2344,12 +2435,10 @@ mod tests {
}
}
- App::test((), |mut app| async move {
- let app = &mut app;
-
+ App::test((), |app| {
let handle_1 = app.add_model(|ctx| Model::new(None, ctx));
let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), ctx));
- assert_eq!(app.0.borrow().ctx.models.len(), 2);
+ assert_eq!(app.ctx.models.len(), 2);
handle_1.update(app, |model, ctx| {
model.events.push("updated".into());
@@ -2357,30 +2446,25 @@ mod tests {
ctx.notify();
ctx.emit(2);
});
- handle_1.read(app, |model, _| {
- assert_eq!(model.events, vec!["updated".to_string()]);
- });
- handle_2.read(app, |model, _| {
- assert_eq!(
- model.events,
- vec![
- "observed event 1".to_string(),
- "notified".to_string(),
- "observed event 2".to_string(),
- ]
- );
- });
+ assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+ assert_eq!(
+ handle_2.read(app).events,
+ vec![
+ "observed event 1".to_string(),
+ "notified".to_string(),
+ "observed event 2".to_string(),
+ ]
+ );
handle_2.update(app, |model, _| {
drop(handle_1);
model.other.take();
});
- let app_state = app.0.borrow();
- assert_eq!(app_state.ctx.models.len(), 1);
- assert!(app_state.subscriptions.is_empty());
- assert!(app_state.observations.is_empty());
- })
+ assert_eq!(app.ctx.models.len(), 1);
+ assert!(app.subscriptions.is_empty());
+ assert!(app.observations.is_empty());
+ });
}
#[test]
@@ -2394,8 +2478,7 @@ mod tests {
type Event = usize;
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let handle_1 = app.add_model(|_| Model::default());
let handle_2 = app.add_model(|_| Model::default());
let handle_2b = handle_2.clone();
@@ -2411,10 +2494,10 @@ mod tests {
});
handle_2.update(app, |_, c| c.emit(7));
- handle_1.read(app, |model, _| assert_eq!(model.events, vec![7]));
+ assert_eq!(handle_1.read(app).events, vec![7]);
handle_2.update(app, |_, c| c.emit(5));
- handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5]));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
})
}
@@ -2430,17 +2513,16 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let handle_1 = app.add_model(|_| Model::default());
let handle_2 = app.add_model(|_| Model::default());
let handle_2b = handle_2.clone();
handle_1.update(app, |_, c| {
c.observe(&handle_2, move |model, observed, c| {
- model.events.push(observed.as_ref(c).count);
+ model.events.push(observed.read(c).count);
c.observe(&handle_2b, |model, observed, c| {
- model.events.push(observed.as_ref(c).count * 2);
+ model.events.push(observed.read(c).count * 2);
});
});
});
@@ -2449,13 +2531,13 @@ mod tests {
model.count = 7;
c.notify()
});
- handle_1.read(app, |model, _| assert_eq!(model.events, vec![7]));
+ assert_eq!(handle_1.read(app).events, vec![7]);
handle_2.update(app, |model, c| {
model.count = 5;
c.notify()
});
- handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5]))
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5])
})
}
@@ -2470,7 +2552,7 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
+ App::test_async((), |mut app| async move {
let handle = app.add_model(|_| Model::default());
handle
.update(&mut app, |_, c| {
@@ -2479,7 +2561,7 @@ mod tests {
})
})
.await;
- handle.read(&app, |model, _| assert_eq!(model.count, 7));
+ app.read(|ctx| assert_eq!(handle.read(ctx).count, 7));
handle
.update(&mut app, |_, c| {
@@ -2488,7 +2570,7 @@ mod tests {
})
})
.await;
- handle.read(&app, |model, _| assert_eq!(model.count, 14));
+ app.read(|ctx| assert_eq!(handle.read(ctx).count, 14));
});
}
@@ -2503,7 +2585,7 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
+ App::test_async((), |mut app| async move {
let handle = app.add_model(|_| Model::default());
handle
.update(&mut app, |_, c| {
@@ -2518,10 +2600,7 @@ mod tests {
)
})
.await;
-
- handle.read(&app, |model, _| {
- assert_eq!(model.events, [Some(1), Some(2), Some(3), None])
- });
+ app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None]));
})
}
@@ -2560,40 +2639,34 @@ mod tests {
}
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let (window_id, _) = app.add_window(|ctx| View::new(None, ctx));
let handle_1 = app.add_view(window_id, |ctx| View::new(None, ctx));
let handle_2 = app.add_view(window_id, |ctx| View::new(Some(handle_1.clone()), ctx));
- assert_eq!(app.0.borrow().ctx.windows[&window_id].views.len(), 3);
+ assert_eq!(app.ctx.windows[&window_id].views.len(), 3);
handle_1.update(app, |view, ctx| {
view.events.push("updated".into());
ctx.emit(1);
ctx.emit(2);
});
- handle_1.read(app, |view, _| {
- assert_eq!(view.events, vec!["updated".to_string()]);
- });
- handle_2.read(app, |view, _| {
- assert_eq!(
- view.events,
- vec![
- "observed event 1".to_string(),
- "observed event 2".to_string(),
- ]
- );
- });
+ assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+ assert_eq!(
+ handle_2.read(app).events,
+ vec![
+ "observed event 1".to_string(),
+ "observed event 2".to_string(),
+ ]
+ );
handle_2.update(app, |view, _| {
drop(handle_1);
view.other.take();
});
- let app_state = app.0.borrow();
- assert_eq!(app_state.ctx.windows[&window_id].views.len(), 2);
- assert!(app_state.subscriptions.is_empty());
- assert!(app_state.observations.is_empty());
+ assert_eq!(app.ctx.windows[&window_id].views.len(), 2);
+ assert!(app.subscriptions.is_empty());
+ assert!(app.observations.is_empty());
})
}
@@ -2624,8 +2697,7 @@ mod tests {
type Event = usize;
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let (window_id, handle_1) = app.add_window(|_| View::default());
let handle_2 = app.add_view(window_id, |_| View::default());
let handle_2b = handle_2.clone();
@@ -2646,13 +2718,13 @@ mod tests {
});
handle_2.update(app, |_, c| c.emit(7));
- handle_1.read(app, |view, _| assert_eq!(view.events, vec![7]));
+ assert_eq!(handle_1.read(app).events, vec![7]);
handle_2.update(app, |_, c| c.emit(5));
- handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5]));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
handle_3.update(app, |_, c| c.emit(9));
- handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5, 9]));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5, 9]);
})
}
@@ -2680,9 +2752,7 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
- let app = &mut app;
-
+ App::test((), |app| {
let (window_id, _) = app.add_window(|_| View);
let observing_view = app.add_view(window_id, |_| View);
let emitting_view = app.add_view(window_id, |_| View);
@@ -2697,7 +2767,7 @@ mod tests {
ctx.subscribe(&observed_model, |_, _, _| {});
});
- app.update(|_| {
+ app.update(|| {
drop(observing_view);
drop(observing_model);
});
@@ -2737,14 +2807,13 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let (_, view) = app.add_window(|_| View::default());
let model = app.add_model(|_| Model::default());
view.update(app, |_, c| {
c.observe(&model, |me, observed, c| {
- me.events.push(observed.as_ref(c).count)
+ me.events.push(observed.read(c).count)
});
});
@@ -2752,7 +2821,7 @@ mod tests {
model.count = 11;
c.notify();
});
- view.read(app, |view, _| assert_eq!(view.events, vec![11]));
+ assert_eq!(view.read(app).events, vec![11]);
})
}
@@ -2780,9 +2849,7 @@ mod tests {
type Event = ();
}
- App::test((), |mut app| async move {
- let app = &mut app;
-
+ App::test((), |app| {
let (window_id, _) = app.add_window(|_| View);
let observing_view = app.add_view(window_id, |_| View);
let observing_model = app.add_model(|_| Model);
@@ -2795,7 +2862,7 @@ mod tests {
ctx.observe(&observed_model, |_, _, _| {});
});
- app.update(|_| {
+ app.update(|| {
drop(observing_view);
drop(observing_model);
});
@@ -2835,8 +2902,7 @@ mod tests {
}
}
- App::test((), |mut app| async move {
- let app = &mut app;
+ App::test((), |app| {
let (window_id, view_1) = app.add_window(|_| View::default());
let view_2 = app.add_view(window_id, |_| View::default());
@@ -2851,18 +2917,16 @@ mod tests {
ctx.focus(&view_1);
});
- view_1.read(app, |view_1, _| {
- assert_eq!(
- view_1.events,
- [
- "self focused".to_string(),
- "self blurred".to_string(),
- "view 2 focused".to_string(),
- "self focused".to_string(),
- "view 2 blurred".to_string(),
- ],
- );
- });
+ assert_eq!(
+ view_1.read(app).events,
+ [
+ "self focused".to_string(),
+ "self blurred".to_string(),
+ "view 2 focused".to_string(),
+ "self focused".to_string(),
+ "view 2 blurred".to_string(),
+ ],
+ );
})
}
@@ -2887,8 +2951,8 @@ mod tests {
}
}
- App::test((), |mut app| async move {
- let (_, handle) = app.add_window(|_| View::default());
+ App::test_async((), |mut app| async move {
+ let handle = app.add_window(|_| View::default()).1;
handle
.update(&mut app, |_, c| {
c.spawn(async { 7 }, |me, output, _| {
@@ -2896,7 +2960,7 @@ mod tests {
})
})
.await;
- handle.read(&app, |view, _| assert_eq!(view.count, 7));
+ app.read(|ctx| assert_eq!(handle.read(ctx).count, 7));
handle
.update(&mut app, |_, c| {
c.spawn(async { 14 }, |me, output, _| {
@@ -2904,7 +2968,7 @@ mod tests {
})
})
.await;
- handle.read(&app, |view, _| assert_eq!(view.count, 14));
+ app.read(|ctx| assert_eq!(handle.read(ctx).count, 14));
});
}
@@ -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,
@@ -1,62 +0,0 @@
-use super::{BoolExt as _, Dispatcher, FontSystem, Window};
-use crate::{executor, platform};
-use anyhow::Result;
-use cocoa::{
- appkit::{NSPasteboard, NSPasteboardTypeString},
- base::{id, nil},
- foundation::NSData,
-};
-use objc::{class, msg_send, sel, sel_impl};
-use std::{ffi::c_void, rc::Rc, sync::Arc};
-
-pub struct App {
- dispatcher: Arc<Dispatcher>,
- fonts: Arc<FontSystem>,
-}
-
-impl App {
- pub fn new() -> Self {
- Self {
- dispatcher: Arc::new(Dispatcher),
- fonts: Arc::new(FontSystem::new()),
- }
- }
-}
-
-impl platform::App for App {
- fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
- self.dispatcher.clone()
- }
-
- fn activate(&self, ignoring_other_apps: bool) {
- unsafe {
- let app: id = msg_send![class!(NSApplication), sharedApplication];
- let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()];
- }
- }
-
- fn open_window(
- &self,
- options: platform::WindowOptions,
- executor: Rc<executor::Foreground>,
- ) -> Result<Box<dyn platform::Window>> {
- Ok(Box::new(Window::open(options, executor, self.fonts())?))
- }
-
- fn fonts(&self) -> Arc<dyn platform::FontSystem> {
- self.fonts.clone()
- }
-
- fn copy(&self, text: &str) {
- unsafe {
- let data = NSData::dataWithBytes_length_(
- nil,
- text.as_ptr() as *const c_void,
- text.len() as u64,
- );
- let pasteboard = NSPasteboard::generalPasteboard(nil);
- pasteboard.clearContents();
- pasteboard.setData_forType(data, NSPasteboardTypeString);
- }
- }
-}
@@ -1,28 +1,22 @@
-mod app;
mod atlas;
mod dispatcher;
mod event;
mod fonts;
mod geometry;
+mod platform;
mod renderer;
-mod runner;
mod sprite_cache;
mod window;
-use crate::platform;
-pub use app::App;
use cocoa::base::{BOOL, NO, YES};
pub use dispatcher::Dispatcher;
pub use fonts::FontSystem;
-pub use runner::Runner;
+use platform::MacPlatform;
+use std::rc::Rc;
use window::Window;
-pub fn app() -> impl platform::App {
- App::new()
-}
-
-pub fn runner() -> impl platform::Runner {
- Runner::new()
+pub fn platform() -> Rc<dyn super::Platform> {
+ Rc::new(MacPlatform::new())
}
trait BoolExt {
@@ -0,0 +1,387 @@
+use super::{BoolExt as _, Dispatcher, FontSystem, Window};
+use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
+use anyhow::Result;
+use cocoa::{
+ appkit::{
+ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+ NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
+ NSPasteboardTypeString, NSWindow,
+ },
+ base::{id, nil, selector},
+ foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL},
+};
+use ctor::ctor;
+use objc::{
+ class,
+ declare::ClassDecl,
+ msg_send,
+ runtime::{Class, Object, Sel},
+ sel, sel_impl,
+};
+use ptr::null_mut;
+use std::{
+ cell::RefCell,
+ ffi::{c_void, CStr},
+ os::raw::c_char,
+ path::PathBuf,
+ ptr,
+ rc::Rc,
+ sync::Arc,
+};
+
+const MAC_PLATFORM_IVAR: &'static str = "platform";
+static mut APP_CLASS: *const Class = ptr::null();
+static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+ APP_CLASS = {
+ let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
+ decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+ decl.add_method(
+ sel!(sendEvent:),
+ send_event as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.register()
+ };
+
+ APP_DELEGATE_CLASS = {
+ let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+ decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+ decl.add_method(
+ sel!(applicationDidFinishLaunching:),
+ did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(applicationDidBecomeActive:),
+ did_become_active as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(applicationDidResignActive:),
+ did_resign_active as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(handleGPUIMenuItem:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(application:openFiles:),
+ open_files as extern "C" fn(&mut Object, Sel, id, id),
+ );
+ decl.register()
+ }
+}
+
+pub struct MacPlatform {
+ dispatcher: Arc<Dispatcher>,
+ fonts: Arc<FontSystem>,
+ callbacks: RefCell<Callbacks>,
+ menu_item_actions: RefCell<Vec<String>>,
+}
+
+#[derive(Default)]
+struct Callbacks {
+ become_active: Option<Box<dyn FnMut()>>,
+ resign_active: Option<Box<dyn FnMut()>>,
+ event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
+ menu_command: Option<Box<dyn FnMut(&str)>>,
+ open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+ finish_launching: Option<Box<dyn FnOnce() -> ()>>,
+}
+
+impl MacPlatform {
+ pub fn new() -> Self {
+ Self {
+ dispatcher: Arc::new(Dispatcher),
+ fonts: Arc::new(FontSystem::new()),
+ callbacks: Default::default(),
+ menu_item_actions: Default::default(),
+ }
+ }
+
+ unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id {
+ let menu_bar = NSMenu::new(nil).autorelease();
+ let mut menu_item_actions = self.menu_item_actions.borrow_mut();
+ menu_item_actions.clear();
+
+ for menu_config in menus {
+ let menu_bar_item = NSMenuItem::new(nil).autorelease();
+ let menu = NSMenu::new(nil).autorelease();
+
+ menu.setTitle_(ns_string(menu_config.name));
+
+ for item_config in menu_config.items {
+ let item;
+
+ match item_config {
+ MenuItem::Separator => {
+ item = NSMenuItem::separatorItem(nil);
+ }
+ MenuItem::Action {
+ name,
+ keystroke,
+ action,
+ } => {
+ if let Some(keystroke) = keystroke {
+ let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
+ panic!(
+ "Invalid keystroke for menu item {}:{} - {:?}",
+ menu_config.name, name, err
+ )
+ });
+
+ let mut mask = NSEventModifierFlags::empty();
+ for (modifier, flag) in &[
+ (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
+ (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
+ (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
+ ] {
+ if *modifier {
+ mask |= *flag;
+ }
+ }
+
+ item = NSMenuItem::alloc(nil)
+ .initWithTitle_action_keyEquivalent_(
+ ns_string(name),
+ selector("handleGPUIMenuItem:"),
+ ns_string(&keystroke.key),
+ )
+ .autorelease();
+ item.setKeyEquivalentModifierMask_(mask);
+ } else {
+ item = NSMenuItem::alloc(nil)
+ .initWithTitle_action_keyEquivalent_(
+ ns_string(name),
+ selector("handleGPUIMenuItem:"),
+ ns_string(""),
+ )
+ .autorelease();
+ }
+
+ let tag = menu_item_actions.len() as NSInteger;
+ let _: () = msg_send![item, setTag: tag];
+ menu_item_actions.push(action.to_string());
+ }
+ }
+
+ menu.addItem_(item);
+ }
+
+ menu_bar_item.setSubmenu_(menu);
+ menu_bar.addItem_(menu_bar_item);
+ }
+
+ menu_bar
+ }
+}
+
+impl platform::Platform for MacPlatform {
+ fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+ self.callbacks.borrow_mut().become_active = Some(callback);
+ }
+
+ fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+ self.callbacks.borrow_mut().resign_active = Some(callback);
+ }
+
+ fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
+ self.callbacks.borrow_mut().event = Some(callback);
+ }
+
+ fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>) {
+ self.callbacks.borrow_mut().menu_command = Some(callback);
+ }
+
+ fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>) {
+ self.callbacks.borrow_mut().open_files = Some(callback);
+ }
+
+ fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>) {
+ 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::<c_void>());
+ (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+ }
+ }
+
+ fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
+ self.dispatcher.clone()
+ }
+
+ fn activate(&self, ignoring_other_apps: bool) {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+ }
+ }
+
+ fn open_window(
+ &self,
+ options: platform::WindowOptions,
+ executor: Rc<executor::Foreground>,
+ ) -> Result<Box<dyn platform::Window>> {
+ Ok(Box::new(Window::open(options, executor, self.fonts())?))
+ }
+
+ fn prompt_for_paths(
+ &self,
+ options: platform::PathPromptOptions,
+ ) -> Option<Vec<std::path::PathBuf>> {
+ unsafe {
+ let panel = NSOpenPanel::openPanel(nil);
+ panel.setCanChooseDirectories_(options.directories.to_objc());
+ panel.setCanChooseFiles_(options.files.to_objc());
+ panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+ panel.setResolvesAliases_(false.to_objc());
+ let response = panel.runModal();
+ if response == NSModalResponse::NSModalResponseOk {
+ let mut result = Vec::new();
+ let urls = panel.URLs();
+ for i in 0..urls.count() {
+ let url = urls.objectAtIndex(i);
+ let string = url.absoluteString();
+ let string = std::ffi::CStr::from_ptr(string.UTF8String())
+ .to_string_lossy()
+ .to_string();
+ if let Some(path) = string.strip_prefix("file://") {
+ result.push(PathBuf::from(path));
+ }
+ }
+ Some(result)
+ } else {
+ None
+ }
+ }
+ }
+
+ fn fonts(&self) -> Arc<dyn platform::FontSystem> {
+ self.fonts.clone()
+ }
+
+ fn quit(&self) {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, terminate: nil];
+ }
+ }
+
+ fn copy(&self, text: &str) {
+ unsafe {
+ let data = NSData::dataWithBytes_length_(
+ nil,
+ text.as_ptr() as *const c_void,
+ text.len() as u64,
+ );
+ let pasteboard = NSPasteboard::generalPasteboard(nil);
+ pasteboard.clearContents();
+ pasteboard.setData_forType(data, NSPasteboardTypeString);
+ }
+ }
+
+ fn set_menus(&self, menus: &[Menu]) {
+ unsafe {
+ let app: id = msg_send![APP_CLASS, sharedApplication];
+ app.setMainMenu_(self.create_menu_bar(menus));
+ }
+ }
+}
+
+unsafe fn get_platform(object: &mut Object) -> &MacPlatform {
+ let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+ assert!(!platform_ptr.is_null());
+ &*(platform_ptr as *const MacPlatform)
+}
+
+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+ unsafe {
+ if let Some(event) = Event::from_native(native_event, None) {
+ let platform = get_platform(this);
+ if let Some(callback) = platform.callbacks.borrow_mut().event.as_mut() {
+ if callback(event) {
+ return;
+ }
+ }
+ }
+
+ msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
+ }
+}
+
+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+ unsafe {
+ let app: id = msg_send![APP_CLASS, sharedApplication];
+ app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
+
+ let platform = get_platform(this);
+ if let Some(callback) = platform.callbacks.borrow_mut().finish_launching.take() {
+ callback();
+ }
+ }
+}
+
+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+ let platform = unsafe { get_platform(this) };
+ if let Some(callback) = platform.callbacks.borrow_mut().become_active.as_mut() {
+ callback();
+ }
+}
+
+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+ let platform = unsafe { get_platform(this) };
+ if let Some(callback) = platform.callbacks.borrow_mut().resign_active.as_mut() {
+ callback();
+ }
+}
+
+extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
+ let paths = unsafe {
+ (0..paths.count())
+ .into_iter()
+ .filter_map(|i| {
+ let path = paths.objectAtIndex(i);
+ match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
+ Ok(string) => Some(PathBuf::from(string)),
+ Err(err) => {
+ log::error!("error converting path to string: {}", err);
+ None
+ }
+ }
+ })
+ .collect::<Vec<_>>()
+ };
+ 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()
+}
@@ -1,188 +0,0 @@
-use crate::platform::Event;
-use cocoa::{
- appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
- base::{id, nil},
- foundation::{NSArray, NSAutoreleasePool, NSString},
-};
-use ctor::ctor;
-use objc::{
- class,
- declare::ClassDecl,
- msg_send,
- runtime::{Class, Object, Sel},
- sel, sel_impl,
-};
-use std::{
- ffi::CStr,
- os::raw::{c_char, c_void},
- path::PathBuf,
- ptr,
-};
-
-const RUNNER_IVAR: &'static str = "runner";
-static mut APP_CLASS: *const Class = ptr::null();
-static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
-
-#[ctor]
-unsafe fn build_classes() {
- APP_CLASS = {
- let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
- decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
- decl.add_method(
- sel!(sendEvent:),
- send_event as extern "C" fn(&mut Object, Sel, id),
- );
- decl.register()
- };
-
- APP_DELEGATE_CLASS = {
- let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
- decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
- decl.add_method(
- sel!(applicationDidFinishLaunching:),
- did_finish_launching as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationDidBecomeActive:),
- did_become_active as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationDidResignActive:),
- did_resign_active as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(application:openFiles:),
- open_files as extern "C" fn(&mut Object, Sel, id, id),
- );
- decl.register()
- }
-}
-
-#[derive(Default)]
-pub struct Runner {
- finish_launching_callback: Option<Box<dyn FnOnce()>>,
- become_active_callback: Option<Box<dyn FnMut()>>,
- resign_active_callback: Option<Box<dyn FnMut()>>,
- event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
- open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
-}
-
-impl Runner {
- pub fn new() -> Self {
- Default::default()
- }
-}
-
-impl crate::platform::Runner for Runner {
- fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
- self.finish_launching_callback = Some(Box::new(callback));
- self
- }
-
- fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
- log::info!("become active");
- self.become_active_callback = Some(Box::new(callback));
- self
- }
-
- fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
- self.resign_active_callback = Some(Box::new(callback));
- self
- }
-
- fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
- self.event_callback = Some(Box::new(callback));
- self
- }
-
- fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
- self.open_files_callback = Some(Box::new(callback));
- self
- }
-
- fn run(self) {
- unsafe {
- let self_ptr = Box::into_raw(Box::new(self));
-
- let pool = NSAutoreleasePool::new(nil);
- let app: id = msg_send![APP_CLASS, sharedApplication];
- let _: () = msg_send![
- app,
- setActivationPolicy: NSApplicationActivationPolicyRegular
- ];
- (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
- let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
- (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
- let _: () = msg_send![app, setDelegate: app_delegate];
- let _: () = msg_send![app, run];
- let _: () = msg_send![pool, drain];
- // The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
- Box::from_raw(self_ptr);
- }
- }
-}
-
-unsafe fn get_runner(object: &mut Object) -> &mut Runner {
- let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
- &mut *(runner_ptr as *mut Runner)
-}
-
-extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
- let event = unsafe { Event::from_native(native_event, None) };
-
- if let Some(event) = event {
- let runner = unsafe { get_runner(this) };
- if let Some(callback) = runner.event_callback.as_mut() {
- if callback(event) {
- return;
- }
- }
- }
-
- unsafe {
- let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
- }
-}
-
-extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
- let runner = unsafe { get_runner(this) };
- if let Some(callback) = runner.finish_launching_callback.take() {
- callback();
- }
-}
-
-extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
- let runner = unsafe { get_runner(this) };
- if let Some(callback) = runner.become_active_callback.as_mut() {
- callback();
- }
-}
-
-extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
- let runner = unsafe { get_runner(this) };
- if let Some(callback) = runner.resign_active_callback.as_mut() {
- callback();
- }
-}
-
-extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
- let paths = unsafe {
- (0..paths.count())
- .into_iter()
- .filter_map(|i| {
- let path = paths.objectAtIndex(i);
- match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
- Ok(string) => Some(PathBuf::from(string)),
- Err(err) => {
- log::error!("error converting path to string: {}", err);
- None
- }
- }
- })
- .collect::<Vec<_>>()
- };
- let runner = unsafe { get_runner(this) };
- if let Some(callback) = runner.open_files_callback.as_mut() {
- callback(paths);
- }
-}
@@ -15,32 +15,34 @@ use crate::{
vector::Vector2F,
},
text_layout::Line,
- Scene,
+ Menu, Scene,
};
use anyhow::Result;
use async_task::Runnable;
pub use event::Event;
use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc};
-pub trait Runner {
- fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self where;
- fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
- fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
- fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
- fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
- fn run(self);
-}
+pub trait Platform {
+ fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>);
+ fn on_become_active(&self, callback: Box<dyn FnMut()>);
+ fn on_resign_active(&self, callback: Box<dyn FnMut()>);
+ fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+ fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
+ fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
-pub trait App {
fn dispatcher(&self) -> Arc<dyn Dispatcher>;
+ fn fonts(&self) -> Arc<dyn FontSystem>;
+
fn activate(&self, ignoring_other_apps: bool);
fn open_window(
&self,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Box<dyn Window>>;
- fn fonts(&self) -> Arc<dyn FontSystem>;
+ fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
+ fn quit(&self);
fn copy(&self, text: &str);
+ fn set_menus(&self, menus: &[Menu]);
}
pub trait Dispatcher: Send + Sync {
@@ -64,6 +66,12 @@ pub struct WindowOptions<'a> {
pub title: Option<&'a str>,
}
+pub struct PathPromptOptions {
+ pub files: bool,
+ pub directories: bool,
+ pub multiple: bool,
+}
+
pub trait FontSystem: Send + Sync {
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>>;
fn select_font(
@@ -2,7 +2,7 @@ use pathfinder_geometry::vector::Vector2F;
use std::rc::Rc;
use std::sync::Arc;
-struct App {
+struct Platform {
dispatcher: Arc<dyn super::Dispatcher>,
fonts: Arc<dyn super::FontSystem>,
}
@@ -17,9 +17,7 @@ pub struct Window {
resize_handlers: Vec<Box<dyn FnMut(&mut dyn super::WindowContext)>>,
}
-pub struct WindowContext {}
-
-impl App {
+impl Platform {
fn new() -> Self {
Self {
dispatcher: Arc::new(Dispatcher),
@@ -28,11 +26,29 @@ impl App {
}
}
-impl super::App for App {
+impl super::Platform for Platform {
+ fn on_menu_command(&self, _: Box<dyn FnMut(&str)>) {}
+
+ fn on_become_active(&self, _: Box<dyn FnMut()>) {}
+
+ fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
+
+ fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
+
+ fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
+
+ fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) {
+ unimplemented!()
+ }
+
fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
self.dispatcher.clone()
}
+ fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
+ self.fonts.clone()
+ }
+
fn activate(&self, _ignoring_other_apps: bool) {}
fn open_window(
@@ -43,8 +59,12 @@ impl super::App for App {
Ok(Box::new(Window::new(options.bounds.size())))
}
- fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
- self.fonts.clone()
+ fn set_menus(&self, _menus: &[crate::Menu]) {}
+
+ fn quit(&self) {}
+
+ fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option<Vec<std::path::PathBuf>> {
+ None
}
fn copy(&self, _: &str) {}
@@ -96,6 +116,6 @@ impl super::Window for Window {
}
}
-pub fn app() -> impl super::App {
- App::new()
+pub fn platform() -> impl super::Platform {
+ Platform::new()
}
@@ -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();
@@ -2315,13 +2315,13 @@ mod tests {
#[test]
fn test_edit_events() {
- App::test((), |mut app| async move {
+ App::test((), |app| {
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
- let ops = buffer1.update(&mut app, |buffer, ctx| {
+ let ops = buffer1.update(app, |buffer, ctx| {
let buffer_1_events = buffer_1_events.clone();
ctx.subscribe(&buffer1, move |_, event, _| {
buffer_1_events.borrow_mut().push(event.clone())
@@ -2333,7 +2333,7 @@ mod tests {
buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap()
});
- buffer2.update(&mut app, |buffer, ctx| {
+ buffer2.update(app, |buffer, ctx| {
buffer.apply_ops(ops, Some(ctx)).unwrap();
});
@@ -2837,12 +2837,12 @@ mod tests {
#[test]
fn test_is_modified() -> Result<()> {
- App::test((), |mut app| async move {
+ App::test((), |app| {
let model = app.add_model(|_| Buffer::new(0, "abc"));
let events = Rc::new(RefCell::new(Vec::new()));
// initially, the buffer isn't dirty.
- model.update(&mut app, |buffer, ctx| {
+ model.update(app, |buffer, ctx| {
ctx.subscribe(&model, {
let events = events.clone();
move |_, event, _| events.borrow_mut().push(event.clone())
@@ -2855,7 +2855,7 @@ mod tests {
});
// after the first edit, the buffer is dirty, and emits a dirtied event.
- model.update(&mut app, |buffer, ctx| {
+ model.update(app, |buffer, ctx| {
assert!(buffer.text() == "ac");
assert!(buffer.is_dirty());
assert_eq!(
@@ -2874,7 +2874,7 @@ mod tests {
});
// after saving, the buffer is not dirty, and emits a saved event.
- model.update(&mut app, |buffer, ctx| {
+ model.update(app, |buffer, ctx| {
assert!(!buffer.is_dirty());
assert_eq!(*events.borrow(), &[Event::Saved]);
events.borrow_mut().clear();
@@ -2884,7 +2884,7 @@ mod tests {
});
// after editing again, the buffer is dirty, and emits another dirty event.
- model.update(&mut app, |buffer, ctx| {
+ model.update(app, |buffer, ctx| {
assert!(buffer.text() == "aBDc");
assert!(buffer.is_dirty());
assert_eq!(
@@ -2910,7 +2910,7 @@ mod tests {
assert!(buffer.is_dirty());
});
- model.update(&mut app, |_, _| {
+ model.update(app, |_, _| {
assert_eq!(
*events.borrow(),
&[Event::Edited(vec![Edit {
@@ -37,7 +37,7 @@ impl BufferElement {
ctx: &mut EventContext,
) -> bool {
if paint.text_bounds.contains_point(position) {
- let view = self.view.as_ref(ctx.app);
+ let view = self.view.read(ctx.app);
let position =
paint.point_for_position(view, layout, position, ctx.font_cache, ctx.app);
ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
@@ -48,7 +48,7 @@ impl BufferElement {
}
fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext) -> bool {
- if self.view.as_ref(ctx.app).is_selecting() {
+ if self.view.read(ctx.app).is_selecting() {
ctx.dispatch_action("buffer:select", SelectAction::End);
true
} else {
@@ -63,7 +63,7 @@ impl BufferElement {
paint: &mut PaintState,
ctx: &mut EventContext,
) -> bool {
- let view = self.view.as_ref(ctx.app);
+ let view = self.view.read(ctx.app);
if view.is_selecting() {
let rect = paint.text_bounds;
@@ -145,7 +145,7 @@ impl BufferElement {
return false;
}
- let view = self.view.as_ref(ctx.app);
+ let view = self.view.read(ctx.app);
let font_cache = &ctx.font_cache;
let layout_cache = &ctx.text_layout_cache;
let max_glyph_width = view.em_width(font_cache);
@@ -167,7 +167,7 @@ impl BufferElement {
}
fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
- let view = self.view.as_ref(ctx.app);
+ let view = self.view.read(ctx.app);
let line_height = view.line_height(ctx.font_cache);
let scroll_top = view.scroll_position().y() * line_height;
@@ -197,7 +197,7 @@ impl BufferElement {
}
fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
- let view = self.view.as_ref(ctx.app);
+ let view = self.view.read(ctx.app);
let line_height = view.line_height(ctx.font_cache);
let descent = view.font_descent(ctx.font_cache);
let start_row = view.scroll_position().y() as u32;
@@ -313,14 +313,14 @@ impl Element for BufferElement {
let app = ctx.app;
let mut size = constraint.max;
if size.y().is_infinite() {
- let view = self.view.as_ref(app);
+ let view = self.view.read(app);
size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache));
}
if size.x().is_infinite() {
unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
}
- let view = self.view.as_ref(app);
+ let view = self.view.read(app);
let font_cache = &ctx.font_cache;
let layout_cache = &ctx.text_layout_cache;
let line_height = view.line_height(font_cache);
@@ -402,9 +402,9 @@ impl Element for BufferElement {
ctx: &mut AfterLayoutContext,
) {
if let Some(layout) = layout {
- let app = ctx.app.downgrade();
+ let app = ctx.app.as_ref();
- let view = self.view.as_ref(app);
+ let view = self.view.read(app);
view.clamp_scroll_left(
layout
.scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app)
@@ -437,7 +437,7 @@ impl Element for BufferElement {
layout.text_size,
);
- if self.view.as_ref(ctx.app).is_gutter_visible() {
+ if self.view.read(ctx.app).is_gutter_visible() {
self.paint_gutter(gutter_bounds, layout, ctx);
}
self.paint_text(text_bounds, layout, ctx);
@@ -6,8 +6,9 @@ use crate::{settings::Settings, watch, workspace};
use anyhow::Result;
use futures_core::future::LocalBoxFuture;
use gpui::{
- fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element,
- ElementBox, Entity, FontCache, ModelHandle, View, ViewContext, WeakViewHandle,
+ fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element,
+ ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
+ WeakViewHandle,
};
use gpui::{geometry::vector::Vector2F, TextLayoutCache};
use parking_lot::Mutex;
@@ -23,7 +24,7 @@ use std::{
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
app.add_bindings(vec![
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
Binding::new("enter", "buffer:newline", Some("BufferView")),
@@ -195,7 +196,7 @@ impl BufferView {
return false;
}
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
let visible_lines = viewport_height / line_height;
let first_cursor_top = self
.selections(app)
@@ -245,7 +246,7 @@ impl BufferView {
layouts: &[Arc<text_layout::Line>],
app: &AppContext,
) {
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
let mut target_left = std::f32::INFINITY;
let mut target_right = 0.0_f32;
@@ -294,7 +295,7 @@ impl BufferView {
ctx.emit(Event::Activate);
}
- let display_map = self.display_map.as_ref(ctx);
+ let display_map = self.display_map.read(ctx);
let cursor = display_map
.anchor_before(position, Bias::Left, ctx.app())
.unwrap();
@@ -319,8 +320,8 @@ impl BufferView {
scroll_position: Vector2F,
ctx: &mut ViewContext<Self>,
) {
- let buffer = self.buffer.as_ref(ctx);
- let map = self.display_map.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
+ let map = self.display_map.read(ctx);
let cursor = map.anchor_before(position, Bias::Left, ctx.app()).unwrap();
if let Some(selection) = self.pending_selection.as_mut() {
selection.set_head(buffer, cursor);
@@ -354,7 +355,7 @@ impl BufferView {
where
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
{
- let map = self.display_map.as_ref(ctx);
+ let map = self.display_map.read(ctx);
let mut selections = Vec::new();
for range in ranges {
selections.push(Selection {
@@ -371,7 +372,7 @@ impl BufferView {
fn insert(&mut self, text: &String, ctx: &mut ViewContext<Self>) {
let mut offset_ranges = SmallVec::<[Range<usize>; 32]>::new();
{
- let buffer = self.buffer.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
for selection in self.selections(ctx.app()) {
let start = selection.start.to_offset(buffer).unwrap();
let end = selection.end.to_offset(buffer).unwrap();
@@ -424,8 +425,8 @@ impl BufferView {
self.start_transaction(ctx);
let mut selections = self.selections(ctx.app()).to_vec();
{
- let buffer = self.buffer.as_ref(ctx);
- let map = self.display_map.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
+ let map = self.display_map.read(ctx);
for selection in &mut selections {
if selection.range(buffer).is_empty() {
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
@@ -459,10 +460,10 @@ impl BufferView {
}
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
- let mut selections = self.selections(ctx.app()).to_vec();
+ let app = ctx.app();
+ let mut selections = self.selections(app).to_vec();
{
- let app = ctx.app();
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let start = selection.start.to_display_point(map, app).unwrap();
let end = selection.end.to_display_point(map, app).unwrap();
@@ -487,8 +488,8 @@ impl BufferView {
pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
let mut selections = self.selections(ctx.app()).to_vec();
{
- let buffer = self.buffer.as_ref(ctx);
- let map = self.display_map.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
+ let map = self.display_map.read(ctx);
for selection in &mut selections {
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
let cursor = map
@@ -510,7 +511,7 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let start = selection.start.to_display_point(map, app).unwrap();
let end = selection.end.to_display_point(map, app).unwrap();
@@ -536,8 +537,8 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let buffer = self.buffer.as_ref(app);
- let map = self.display_map.as_ref(app);
+ let buffer = self.buffer.read(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
let cursor = map
@@ -558,7 +559,7 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let start = selection.start.to_display_point(map, app).unwrap();
let end = selection.end.to_display_point(map, app).unwrap();
@@ -587,8 +588,8 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let buffer = self.buffer.as_ref(app);
- let map = self.display_map.as_ref(app);
+ let buffer = self.buffer.read(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let head = selection.head().to_display_point(map, app).unwrap();
let (head, goal_column) =
@@ -609,7 +610,7 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let start = selection.start.to_display_point(map, app).unwrap();
let end = selection.end.to_display_point(map, app).unwrap();
@@ -638,8 +639,8 @@ impl BufferView {
let mut selections = self.selections(ctx.app()).to_vec();
{
let app = ctx.app();
- let buffer = self.buffer.as_ref(app);
- let map = self.display_map.as_ref(app);
+ let buffer = self.buffer.read(app);
+ let map = self.display_map.read(app);
for selection in &mut selections {
let head = selection.head().to_display_point(map, app).unwrap();
let (head, goal_column) =
@@ -663,14 +664,14 @@ impl BufferView {
self.selections(app)
.first()
.unwrap()
- .display_range(self.display_map.as_ref(app), app)
+ .display_range(self.display_map.read(app), app)
}
pub fn last_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
self.selections(app)
.last()
.unwrap()
- .display_range(self.display_map.as_ref(app), app)
+ .display_range(self.display_map.read(app), app)
}
pub fn selections_in_range<'a>(
@@ -678,7 +679,7 @@ impl BufferView {
range: Range<DisplayPoint>,
app: &'a AppContext,
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
let start = map.anchor_before(range.start, Bias::Left, app).unwrap();
let start_index = self.selection_insertion_index(&start, app);
@@ -698,7 +699,7 @@ impl BufferView {
}
fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
let selections = self.selections(app);
match selections.binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap()) {
Ok(index) => index,
@@ -716,14 +717,14 @@ impl BufferView {
fn selections<'a>(&self, app: &'a AppContext) -> &'a [Selection] {
self.buffer
- .as_ref(app)
+ .read(app)
.selections(self.selection_set_id)
.unwrap()
}
fn update_selections(&self, mut selections: Vec<Selection>, ctx: &mut ViewContext<Self>) {
// Merge overlapping selections.
- let buffer = self.buffer.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
let mut i = 1;
while i < selections.len() {
if selections[i - 1]
@@ -781,7 +782,7 @@ impl BufferView {
let mut fold_ranges = Vec::new();
let app = ctx.app();
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
for selection in self.selections(app) {
let (start, end) = selection.display_range(map, app).sorted();
let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row;
@@ -811,8 +812,8 @@ impl BufferView {
use super::RangeExt;
let app = ctx.app();
- let map = self.display_map.as_ref(app);
- let buffer = self.buffer.as_ref(app);
+ let map = self.display_map.read(app);
+ let buffer = self.buffer.read(app);
let ranges = self
.selections(app)
.iter()
@@ -857,7 +858,7 @@ impl BufferView {
let mut is_blank = true;
for c in self
.display_map
- .as_ref(app)
+ .read(app)
.chars_at(DisplayPoint::new(display_row, 0), app)?
{
if c == ' ' {
@@ -871,7 +872,7 @@ impl BufferView {
}
fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result<Range<Point>> {
- let map = self.display_map.as_ref(app);
+ let map = self.display_map.read(app);
let max_point = self.max_point(app);
let (start_indent, _) = self.line_indent(start_row, app)?;
@@ -892,7 +893,7 @@ impl BufferView {
pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
self.display_map.update(ctx, |map, ctx| {
- let buffer = self.buffer.as_ref(ctx);
+ let buffer = self.buffer.read(ctx);
let ranges = self
.selections(ctx.app())
.iter()
@@ -903,23 +904,23 @@ impl BufferView {
}
pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
- self.display_map.as_ref(app).line(display_row, app)
+ self.display_map.read(app).line(display_row, app)
}
pub fn line_len(&self, display_row: u32, app: &AppContext) -> Result<u32> {
- self.display_map.as_ref(app).line_len(display_row, app)
+ self.display_map.read(app).line_len(display_row, app)
}
pub fn rightmost_point(&self, app: &AppContext) -> DisplayPoint {
- self.display_map.as_ref(app).rightmost_point()
+ self.display_map.read(app).rightmost_point()
}
pub fn max_point(&self, app: &AppContext) -> DisplayPoint {
- self.display_map.as_ref(app).max_point(app)
+ self.display_map.read(app).max_point(app)
}
pub fn text(&self, app: &AppContext) -> String {
- self.display_map.as_ref(app).text(app)
+ self.display_map.read(app).text(app)
}
pub fn font_size(&self) -> f32 {
@@ -963,7 +964,7 @@ impl BufferView {
let font_size = settings.buffer_font_size;
let font_id =
font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
- let digit_count = ((self.buffer.as_ref(app).max_point().row + 1) as f32)
+ let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32)
.log10()
.floor() as usize
+ 1;
@@ -984,7 +985,7 @@ impl BufferView {
layout_cache: &TextLayoutCache,
app: &AppContext,
) -> Result<Vec<Arc<text_layout::Line>>> {
- let display_map = self.display_map.as_ref(app);
+ let display_map = self.display_map.read(app);
let settings = smol::block_on(self.settings.read());
let font_size = settings.buffer_font_size;
@@ -1020,7 +1021,7 @@ impl BufferView {
layout_cache: &TextLayoutCache,
app: &AppContext,
) -> Result<Vec<Arc<text_layout::Line>>> {
- let display_map = self.display_map.as_ref(app);
+ let display_map = self.display_map.read(app);
rows.end = cmp::min(rows.end, display_map.max_point(app).row() + 1);
if rows.start >= rows.end {
@@ -1203,7 +1204,7 @@ impl workspace::ItemView for BufferView {
}
fn title(&self, app: &AppContext) -> std::string::String {
- if let Some(path) = self.buffer.as_ref(app).path(app) {
+ if let Some(path) = self.buffer.read(app).path(app) {
path.file_name()
.expect("buffer's path is always to a file")
.to_string_lossy()
@@ -1214,7 +1215,7 @@ impl workspace::ItemView for BufferView {
}
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
- self.buffer.as_ref(app).entry_id()
+ self.buffer.read(app).entry_id()
}
fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
@@ -1231,7 +1232,7 @@ impl workspace::ItemView for BufferView {
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
- self.buffer.as_ref(ctx).is_dirty()
+ self.buffer.read(ctx).is_dirty()
}
}
@@ -1240,112 +1241,125 @@ mod tests {
use super::*;
use crate::{editor::Point, settings, test::sample_text};
use anyhow::Error;
+ use gpui::App;
use unindent::Unindent;
#[test]
fn test_selection_with_mouse() {
- App::test((), |mut app| async move {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, buffer_view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+ );
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+ );
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+ );
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.end_selection(ctx);
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+ );
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [
- DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
- ]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [
+ DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+ ]
+ );
- buffer_view.update(&mut app, |view, ctx| {
+ buffer_view.update(app, |view, ctx| {
view.end_selection(ctx);
});
- buffer_view.read(&app, |view, app| {
- let selections = view
- .selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
- );
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+ );
});
}
#[test]
- fn test_layout_line_numbers() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_layout_line_numbers() {
+ App::test((), |app| {
let layout_cache = TextLayoutCache::new(app.platform().fonts());
- let font_cache = app.font_cache();
+ let font_cache = app.font_cache().clone();
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
@@ -1353,19 +1367,17 @@ mod tests {
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
- view.read(&app, |view, app| {
- let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?;
- assert_eq!(layouts.len(), 6);
- Result::<()>::Ok(())
- })?;
-
- Ok(())
+ let layouts = view
+ .read(app)
+ .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
+ .unwrap();
+ assert_eq!(layouts.len(), 6);
})
}
#[test]
- fn test_fold() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_fold() {
+ App::test((), |app| {
let buffer = app.add_model(|_| {
Buffer::new(
0,
@@ -1393,8 +1405,9 @@ mod tests {
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
- view.update(&mut app, |view, ctx| {
- view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)?;
+ view.update(app, |view, ctx| {
+ view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
+ .unwrap();
view.fold(&(), ctx);
assert_eq!(
view.text(ctx.app()),
@@ -1448,24 +1461,20 @@ mod tests {
);
view.unfold(&(), ctx);
- assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text());
-
- Ok::<(), Error>(())
- })?;
-
- Ok(())
- })
+ assert_eq!(view.text(ctx.app()), buffer.read(ctx).text());
+ });
+ });
}
#[test]
fn test_move_cursor() -> Result<()> {
- App::test((), |mut app| async move {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
- buffer.update(&mut app, |buffer, ctx| {
+ buffer.update(app, |buffer, ctx| {
buffer.edit(
vec![
Point::new(1, 0)..Point::new(1, 0),
@@ -1476,7 +1485,7 @@ mod tests {
)
})?;
- view.update(&mut app, |view, ctx| {
+ view.update(app, |view, ctx| {
view.move_down(&(), ctx);
assert_eq!(
view.selection_ranges(ctx.app()),
@@ -1495,8 +1504,8 @@ mod tests {
}
#[test]
- fn test_backspace() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_backspace() {
+ App::test((), |app| {
let buffer = app.add_model(|_| {
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
});
@@ -1504,7 +1513,7 @@ mod tests {
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
- view.update(&mut app, |view, ctx| -> Result<()> {
+ view.update(app, |view, ctx| {
view.select_ranges(
&[
// an empty selection - the preceding character is deleted
@@ -1515,17 +1524,15 @@ mod tests {
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
],
ctx,
- )?;
+ )
+ .unwrap();
view.backspace(&(), ctx);
- Ok(())
- })?;
-
- buffer.read(&mut app, |buffer, _| -> Result<()> {
- assert_eq!(buffer.text(), "oe two three\nfou five six\nseven ten\n");
- Ok(())
- })?;
+ });
- Ok(())
+ assert_eq!(
+ buffer.read(app).text(),
+ "oe two three\nfou five six\nseven ten\n"
+ );
})
}
@@ -22,7 +22,7 @@ pub struct FoldMap {
impl FoldMap {
pub fn new(buffer: ModelHandle<Buffer>, app: &AppContext) -> Self {
- let text_summary = buffer.as_ref(app).text_summary();
+ let text_summary = buffer.read(app).text_summary();
Self {
buffer,
folds: Vec::new(),
@@ -72,7 +72,7 @@ impl FoldMap {
let offset = self.to_display_offset(point, app)?;
let mut cursor = self.transforms.cursor();
cursor.seek(&offset, SeekBias::Right);
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
Ok(Chars {
cursor,
offset: offset.0,
@@ -95,7 +95,7 @@ impl FoldMap {
app: &AppContext,
) -> Result<()> {
let mut edits = Vec::new();
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
for range in ranges.into_iter() {
let start = range.start.to_offset(buffer)?;
let end = range.end.to_offset(buffer)?;
@@ -124,7 +124,7 @@ impl FoldMap {
ranges: impl IntoIterator<Item = Range<T>>,
app: &AppContext,
) -> Result<()> {
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
let mut edits = Vec::new();
for range in ranges.into_iter() {
@@ -184,7 +184,7 @@ impl FoldMap {
.ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
assert!(transform.display_text.is_none());
let end_buffer_offset =
- (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.as_ref(app))?;
+ (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))?;
offset += end_buffer_offset - cursor.start().buffer.chars;
}
Ok(DisplayOffset(offset))
@@ -208,7 +208,7 @@ impl FoldMap {
}
pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> {
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
let mut edits = edits.iter().cloned().peekable();
let mut new_transforms = SumTree::new();
@@ -469,115 +469,106 @@ mod tests {
use gpui::App;
#[test]
- fn test_basic_folds() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_basic_folds() {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
- let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
-
- app.read(|app| {
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(2, 4)..Point::new(4, 1),
- ],
- app,
- )?;
- assert_eq!(map.text(app), "aa…cc…eeeee");
- Ok::<(), anyhow::Error>(())
- })?;
-
- let edits = buffer.update(&mut app, |buffer, ctx| {
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(2, 4)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
+
+ let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone();
- buffer.edit(
- vec![
- Point::new(0, 0)..Point::new(0, 1),
- Point::new(2, 3)..Point::new(2, 3),
- ],
- "123",
- Some(ctx),
- )?;
- Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
- })?;
-
- app.read(|app| {
- map.apply_edits(&edits, app)?;
- assert_eq!(map.text(app), "123a…c123c…eeeee");
- Ok::<(), anyhow::Error>(())
- })?;
-
- let edits = buffer.update(&mut app, |buffer, ctx| {
- let start_version = buffer.version.clone();
- buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))?;
- Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
- })?;
+ buffer
+ .edit(
+ vec![
+ Point::new(0, 0)..Point::new(0, 1),
+ Point::new(2, 3)..Point::new(2, 3),
+ ],
+ "123",
+ Some(ctx),
+ )
+ .unwrap();
+ buffer.edits_since(start_version).collect::<Vec<_>>()
+ });
- app.read(|app| {
- map.apply_edits(&edits, app)?;
- assert_eq!(map.text(app), "123a…c123456eee");
+ map.apply_edits(&edits, app.as_ref()).unwrap();
+ assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
+
+ let edits = buffer.update(app, |buffer, ctx| {
+ let start_version = buffer.version.clone();
+ buffer
+ .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
+ .unwrap();
+ buffer.edits_since(start_version).collect::<Vec<_>>()
+ });
- map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?;
- assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee");
+ map.apply_edits(&edits, app.as_ref()).unwrap();
+ assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
- Ok(())
- })
- })
+ map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
+ });
}
#[test]
- fn test_overlapping_folds() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_overlapping_folds() {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
- app.read(|app| {
- let mut map = FoldMap::new(buffer.clone(), app);
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(0, 4)..Point::new(1, 0),
- Point::new(1, 2)..Point::new(3, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app,
- )?;
- assert_eq!(map.text(app), "aa…eeeee");
- Ok(())
- })
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(0, 4)..Point::new(1, 0),
+ Point::new(1, 2)..Point::new(3, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…eeeee");
})
}
#[test]
- fn test_merging_folds_via_edit() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_merging_folds_via_edit() {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
- let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
-
- app.read(|app| {
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app,
- )?;
- assert_eq!(map.text(app), "aa…cccc\nd…eeeee");
- Ok::<(), anyhow::Error>(())
- })?;
-
- let edits = buffer.update(&mut app, |buffer, ctx| {
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
+
+ let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone();
- buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))?;
- Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
- })?;
-
- app.read(|app| {
- map.apply_edits(&edits, app)?;
- assert_eq!(map.text(app), "aa…eeeee");
- Ok(())
- })
- })
+ buffer
+ .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
+ .unwrap();
+ buffer.edits_since(start_version).collect::<Vec<_>>()
+ });
+
+ map.apply_edits(&edits, app.as_ref()).unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…eeeee");
+ });
}
#[test]
- fn test_random_folds() -> Result<()> {
+ fn test_random_folds() {
use crate::editor::ToPoint;
use crate::util::RandomCharIter;
use rand::prelude::*;
@@ -597,16 +588,16 @@ mod tests {
println!("{:?}", seed);
let mut rng = StdRng::seed_from_u64(seed);
- App::test((), |mut app| async move {
+ App::test((), |app| {
let buffer = app.add_model(|_| {
let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
Buffer::new(0, text)
});
- let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
- app.read(|app| {
- let buffer = buffer.as_ref(app);
+ {
+ let buffer = buffer.read(app);
let fold_count = rng.gen_range(0..10);
let mut fold_ranges: Vec<Range<usize>> = Vec::new();
@@ -616,93 +607,83 @@ mod tests {
fold_ranges.push(start..end);
}
- map.fold(fold_ranges, app)?;
+ map.fold(fold_ranges, app.as_ref()).unwrap();
let mut expected_text = buffer.text();
- for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
+ for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
expected_text.replace_range(fold_range.start..fold_range.end, "…");
}
- assert_eq!(map.text(app), expected_text);
+ assert_eq!(map.text(app.as_ref()), expected_text);
- for fold_range in map.merged_fold_ranges(app) {
+ for fold_range in map.merged_fold_ranges(app.as_ref()) {
let display_point =
map.to_display_point(fold_range.start.to_point(buffer).unwrap());
assert!(map.is_line_folded(display_point.row()));
}
+ }
- Ok::<(), anyhow::Error>(())
- })?;
-
- let edits = buffer.update(&mut app, |buffer, ctx| {
+ let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..10);
buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
- Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
- })?;
-
- app.read(|app| {
- map.apply_edits(&edits, app)?;
-
- let buffer = map.buffer.as_ref(app);
- let mut expected_text = buffer.text();
- let mut expected_buffer_rows = Vec::new();
- let mut next_row = buffer.max_point().row;
- for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
- let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
- let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
- expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
- next_row = fold_start.row;
+ buffer.edits_since(start_version).collect::<Vec<_>>()
+ });
- expected_text.replace_range(fold_range.start..fold_range.end, "…");
- }
- expected_buffer_rows.extend((0..=next_row).rev());
- expected_buffer_rows.reverse();
+ map.apply_edits(&edits, app.as_ref()).unwrap();
- assert_eq!(map.text(app), expected_text);
+ let buffer = map.buffer.read(app);
+ let mut expected_text = buffer.text();
+ let mut expected_buffer_rows = Vec::new();
+ let mut next_row = buffer.max_point().row;
+ for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
+ let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
+ let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
+ expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
+ next_row = fold_start.row;
- for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
- let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
- assert_eq!(
- map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
- expected_buffer_rows[idx..],
- );
- }
+ expected_text.replace_range(fold_range.start..fold_range.end, "…");
+ }
+ expected_buffer_rows.extend((0..=next_row).rev());
+ expected_buffer_rows.reverse();
- Ok::<(), anyhow::Error>(())
- })?;
+ assert_eq!(map.text(app.as_ref()), expected_text);
- Ok::<(), anyhow::Error>(())
- })?;
+ for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
+ let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
+ assert_eq!(
+ map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
+ expected_buffer_rows[idx..],
+ );
+ }
+ });
}
-
- Ok(())
}
#[test]
- fn test_buffer_rows() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_buffer_rows() {
+ App::test((), |app| {
let text = sample_text(6, 6) + "\n";
let buffer = app.add_model(|_| Buffer::new(0, text));
- app.read(|app| {
- let mut map = FoldMap::new(buffer.clone(), app);
-
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app,
- )?;
-
- assert_eq!(map.text(app), "aa…cccc\nd…eeeee\nffffff\n");
- assert_eq!(map.buffer_rows(0)?.collect::<Vec<_>>(), vec![0, 3, 5, 6]);
- assert_eq!(map.buffer_rows(3)?.collect::<Vec<_>>(), vec![6]);
-
- Ok(())
- })
- })
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
+ assert_eq!(
+ map.buffer_rows(0).unwrap().collect::<Vec<_>>(),
+ vec![0, 3, 5, 6]
+ );
+ assert_eq!(map.buffer_rows(3).unwrap().collect::<Vec<_>>(), vec![6]);
+ });
}
impl FoldMap {
@@ -713,7 +694,7 @@ mod tests {
}
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
- let buffer = self.buffer.as_ref(app);
+ let buffer = self.buffer.read(app);
let mut fold_ranges = self
.folds
.iter()
@@ -108,7 +108,7 @@ impl DisplayMap {
app: &AppContext,
) -> Result<Anchor> {
self.buffer
- .as_ref(app)
+ .read(app)
.anchor_before(point.to_buffer_point(self, bias, app)?)
}
@@ -119,7 +119,7 @@ impl DisplayMap {
app: &AppContext,
) -> Result<Anchor> {
self.buffer
- .as_ref(app)
+ .read(app)
.anchor_after(point.to_buffer_point(self, bias, app)?)
}
@@ -206,7 +206,7 @@ impl Point {
impl Anchor {
pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
- self.to_point(map.buffer.as_ref(app))?
+ self.to_point(map.buffer.read(app))?
.to_display_point(map, app)
}
}
@@ -292,52 +292,51 @@ pub fn collapse_tabs(
mod tests {
use super::*;
use crate::test::*;
- use anyhow::Error;
use gpui::App;
#[test]
- fn test_chars_at() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_chars_at() {
+ App::test((), |app| {
let text = sample_text(6, 6);
let buffer = app.add_model(|_| Buffer::new(0, text));
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
- buffer.update(&mut app, |buffer, ctx| {
- buffer.edit(
- vec![
- Point::new(1, 0)..Point::new(1, 0),
- Point::new(1, 1)..Point::new(1, 1),
- Point::new(2, 1)..Point::new(2, 1),
- ],
- "\t",
- Some(ctx),
- )
- })?;
-
- map.read(&app, |map, ctx| {
- assert_eq!(
- map.chars_at(DisplayPoint::new(1, 0), ctx)?
- .take(10)
- .collect::<String>(),
- " b bb"
- );
- assert_eq!(
- map.chars_at(DisplayPoint::new(1, 2), ctx)?
- .take(10)
- .collect::<String>(),
- " b bbbb"
- );
- assert_eq!(
- map.chars_at(DisplayPoint::new(1, 6), ctx)?
- .take(13)
- .collect::<String>(),
- " bbbbb\nc c"
- );
-
- Ok::<(), Error>(())
- })?;
-
- Ok(())
- })
+ buffer
+ .update(app, |buffer, ctx| {
+ buffer.edit(
+ vec![
+ Point::new(1, 0)..Point::new(1, 0),
+ Point::new(1, 1)..Point::new(1, 1),
+ Point::new(2, 1)..Point::new(2, 1),
+ ],
+ "\t",
+ Some(ctx),
+ )
+ })
+ .unwrap();
+
+ let map = map.read(app);
+ assert_eq!(
+ map.chars_at(DisplayPoint::new(1, 0), app.as_ref())
+ .unwrap()
+ .take(10)
+ .collect::<String>(),
+ " b bb"
+ );
+ assert_eq!(
+ map.chars_at(DisplayPoint::new(1, 2), app.as_ref())
+ .unwrap()
+ .take(10)
+ .collect::<String>(),
+ " b bbbb"
+ );
+ assert_eq!(
+ map.chars_at(DisplayPoint::new(1, 6), app.as_ref())
+ .unwrap()
+ .take(13)
+ .collect::<String>(),
+ " bbbbb\nc c"
+ );
+ });
}
#[test]
@@ -364,14 +363,14 @@ mod tests {
}
#[test]
- fn test_max_point() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_max_point() {
+ App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
- map.read(&app, |map, app| {
- assert_eq!(map.max_point(app), DisplayPoint::new(1, 11))
- });
- Ok(())
- })
+ assert_eq!(
+ map.read(app).max_point(app.as_ref()),
+ DisplayPoint::new(1, 11)
+ )
+ });
}
}
@@ -11,8 +11,8 @@ use gpui::{
fonts::{Properties, Weight},
geometry::vector::vec2f,
keymap::{self, Binding},
- App, AppContext, Axis, Border, Entity, ModelHandle, View, ViewContext, ViewHandle,
- WeakViewHandle,
+ AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
+ ViewHandle, WeakViewHandle,
};
use std::cmp;
@@ -28,7 +28,7 @@ pub struct FileFinder {
list_state: UniformListState,
}
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
app.add_action("file_finder:toggle", FileFinder::toggle);
app.add_action("file_finder:confirm", FileFinder::confirm);
app.add_action("file_finder:select", FileFinder::select);
@@ -114,7 +114,7 @@ impl FileFinder {
self.matches.len(),
move |mut range, items, app| {
let finder = handle.upgrade(app).unwrap();
- let finder = finder.as_ref(app);
+ let finder = finder.read(app);
let start = range.start;
range.end = cmp::min(range.end, finder.matches.len());
items.extend(finder.matches[range].iter().enumerate().filter_map(
@@ -287,7 +287,7 @@ impl FileFinder {
}
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
- self.spawn_search(self.query_buffer.as_ref(ctx).text(ctx.app()), ctx);
+ self.spawn_search(self.query_buffer.read(ctx).text(ctx.app()), ctx);
}
fn on_query_buffer_event(
@@ -299,7 +299,7 @@ impl FileFinder {
use buffer_view::Event::*;
match event {
Edited => {
- let query = self.query_buffer.as_ref(ctx).text(ctx.app());
+ let query = self.query_buffer.read(ctx).text(ctx.app());
if query.is_empty() {
self.latest_search_id = util::post_inc(&mut self.search_count);
self.matches.clear();
@@ -371,18 +371,18 @@ impl FileFinder {
fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> {
self.workspace
- .as_ref(app)
+ .read(app)
.worktrees()
.get(&tree_id)
- .map(|worktree| worktree.as_ref(app))
+ .map(|worktree| worktree.read(app))
}
fn worktrees(&self, app: &AppContext) -> Vec<Worktree> {
self.workspace
- .as_ref(app)
+ .read(app)
.worktrees()
.iter()
- .map(|worktree| worktree.as_ref(app).clone())
+ .map(|worktree| worktree.read(app).clone())
.collect()
}
}
@@ -394,20 +394,25 @@ mod tests {
editor, settings,
workspace::{Workspace, WorkspaceView},
};
- use anyhow::Result;
use gpui::App;
use smol::fs;
use tempdir::TempDir;
#[test]
- fn test_matching_paths() -> Result<()> {
- App::test((), |mut app| async move {
- let tmp_dir = TempDir::new("example")?;
- fs::create_dir(tmp_dir.path().join("a")).await?;
- fs::write(tmp_dir.path().join("a/banana"), "banana").await?;
- fs::write(tmp_dir.path().join("a/bandana"), "bandana").await?;
- super::init(&mut app);
- editor::init(&mut app);
+ fn test_matching_paths() {
+ App::test_async((), |mut app| async move {
+ let tmp_dir = TempDir::new("example").unwrap();
+ fs::create_dir(tmp_dir.path().join("a")).await.unwrap();
+ fs::write(tmp_dir.path().join("a/banana"), "banana")
+ .await
+ .unwrap();
+ fs::write(tmp_dir.path().join("a/bandana"), "bandana")
+ .await
+ .unwrap();
+ app.update(|ctx| {
+ super::init(ctx);
+ editor::init(ctx);
+ });
let settings = settings::channel(&app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx));
@@ -420,16 +425,17 @@ mod tests {
"file_finder:toggle".into(),
(),
);
- let (finder, query_buffer) = workspace_view.read(&app, |view, ctx| {
- let finder = view
+
+ let finder = app.read(|ctx| {
+ workspace_view
+ .read(ctx)
.modal()
.cloned()
.unwrap()
.downcast::<FileFinder>()
- .unwrap();
- let query_buffer = finder.as_ref(ctx).query_buffer.clone();
- (finder, query_buffer)
+ .unwrap()
});
+ let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
let chain = vec![finder.id(), query_buffer.id()];
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
@@ -452,7 +458,7 @@ mod tests {
// (),
// );
// app.finish_pending_tasks().await; // Load Buffer and open BufferView.
- // let active_pane = workspace_view.read(&app, |view, _| view.active_pane().clone());
+ // let active_pane = workspace_view.as_ref(app).active_pane().clone();
// assert_eq!(
// active_pane.state(&app),
// pane::State {
@@ -462,7 +468,6 @@ mod tests {
// }]
// }
// );
- Ok(())
- })
+ });
}
}
@@ -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;
@@ -1,10 +1,10 @@
use fs::OpenOptions;
-use gpui::platform::{current as platform, Runner as _};
+use gpui::PathPromptOptions;
use log::LevelFilter;
use simplelog::SimpleLogger;
use std::{fs, path::PathBuf};
use zed::{
- assets, editor, file_finder, settings,
+ assets, editor, file_finder, menus, settings,
workspace::{self, OpenParams},
};
@@ -13,32 +13,48 @@ fn main() {
let app = gpui::App::new(assets::Assets).unwrap();
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
-
- {
- let mut app = app.clone();
- platform::runner()
- .on_finish_launching(move || {
- workspace::init(&mut app);
- editor::init(&mut app);
- file_finder::init(&mut app);
-
- if stdout_is_a_pty() {
- app.platform().activate(true);
- }
-
- let paths = collect_path_args();
- if !paths.is_empty() {
- app.dispatch_global_action(
+ app.set_menus(menus::MENUS);
+ app.on_menu_command({
+ let settings_rx = settings_rx.clone();
+ move |command, ctx| match command {
+ "app:open" => {
+ if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions {
+ files: true,
+ directories: true,
+ multiple: true,
+ }) {
+ ctx.dispatch_global_action(
"workspace:open_paths",
OpenParams {
paths,
- settings: settings_rx,
+ settings: settings_rx.clone(),
},
);
}
- })
- .run();
- }
+ }
+ _ => ctx.dispatch_global_action(command, ()),
+ }
+ })
+ .run(move |ctx| {
+ workspace::init(ctx);
+ editor::init(ctx);
+ file_finder::init(ctx);
+
+ if stdout_is_a_pty() {
+ ctx.platform().activate(true);
+ }
+
+ let paths = collect_path_args();
+ if !paths.is_empty() {
+ ctx.dispatch_global_action(
+ "workspace:open_paths",
+ OpenParams {
+ paths,
+ settings: settings_rx,
+ },
+ );
+ }
+ });
}
fn init_logger() {
@@ -0,0 +1,60 @@
+use gpui::{Menu, MenuItem};
+
+#[cfg(target_os = "macos")]
+pub const MENUS: &'static [Menu] = &[
+ Menu {
+ name: "Zed",
+ items: &[
+ MenuItem::Action {
+ name: "About Zed…",
+ keystroke: None,
+ action: "app:about-zed",
+ },
+ MenuItem::Separator,
+ MenuItem::Action {
+ name: "Quit",
+ keystroke: Some("cmd-q"),
+ action: "app:quit",
+ },
+ ],
+ },
+ Menu {
+ name: "File",
+ items: &[MenuItem::Action {
+ name: "Open…",
+ keystroke: Some("cmd-o"),
+ action: "app:open",
+ }],
+ },
+ Menu {
+ name: "Edit",
+ items: &[
+ MenuItem::Action {
+ name: "Undo",
+ keystroke: Some("cmd-z"),
+ action: "editor:undo",
+ },
+ MenuItem::Action {
+ name: "Redo",
+ keystroke: Some("cmd-Z"),
+ action: "editor:redo",
+ },
+ MenuItem::Separator,
+ MenuItem::Action {
+ name: "Cut",
+ keystroke: Some("cmd-x"),
+ action: "editor:cut",
+ },
+ MenuItem::Action {
+ name: "Copy",
+ keystroke: Some("cmd-c"),
+ action: "editor:copy",
+ },
+ MenuItem::Action {
+ name: "Paste",
+ keystroke: Some("cmd-v"),
+ action: "editor:paste",
+ },
+ ],
+ },
+];
@@ -9,11 +9,12 @@ pub use workspace::*;
pub use workspace_view::*;
use crate::{settings::Settings, watch};
-use gpui::{App, MutableAppContext};
+use gpui::MutableAppContext;
use std::path::PathBuf;
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
app.add_global_action("workspace:open_paths", open_paths);
+ app.add_global_action("app:quit", quit);
pane::init(app);
workspace_view::init(app);
}
@@ -50,6 +51,10 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) {
app.add_window(|ctx| WorkspaceView::new(workspace, params.settings.clone(), ctx));
}
+fn quit(_: &(), app: &mut MutableAppContext) {
+ app.platform().quit();
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -59,10 +64,10 @@ mod tests {
#[test]
fn test_open_paths_action() {
- App::test((), |mut app| async move {
+ App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
- init(&mut app);
+ init(app);
let dir = temp_tree(json!({
"a": {
@@ -89,7 +94,7 @@ mod tests {
settings: settings.clone(),
},
);
- assert_eq!(app.window_ids().len(), 1);
+ assert_eq!(app.window_ids().count(), 1);
app.dispatch_global_action(
"workspace:open_paths",
@@ -98,11 +103,19 @@ mod tests {
settings: settings.clone(),
},
);
- assert_eq!(app.window_ids().len(), 1);
- let workspace_view_1 = app.root_view::<WorkspaceView>(app.window_ids()[0]).unwrap();
- workspace_view_1.read(&app, |view, app| {
- assert_eq!(view.workspace.as_ref(app).worktrees().len(), 2);
- });
+ assert_eq!(app.window_ids().count(), 1);
+ let workspace_view_1 = app
+ .root_view::<WorkspaceView>(app.window_ids().next().unwrap())
+ .unwrap();
+ assert_eq!(
+ workspace_view_1
+ .read(app)
+ .workspace
+ .read(app)
+ .worktrees()
+ .len(),
+ 2
+ );
app.dispatch_global_action(
"workspace:open_paths",
@@ -114,7 +127,7 @@ mod tests {
settings: settings.clone(),
},
);
- assert_eq!(app.window_ids().len(), 2);
+ assert_eq!(app.window_ids().count(), 2);
});
}
}
@@ -5,11 +5,11 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
keymap::Binding,
- App, AppContext, Border, Entity, Quad, View, ViewContext,
+ AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
};
use std::cmp;
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
app.add_action(
"pane:activate_item",
|pane: &mut Pane, index: &usize, ctx| {
@@ -101,7 +101,7 @@ impl Workspace {
pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
self.worktrees
.iter()
- .any(|worktree| worktree.as_ref(app).contains_path(path))
+ .any(|worktree| worktree.read(app).contains_path(path))
}
pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
@@ -112,7 +112,7 @@ impl Workspace {
pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
for tree in self.worktrees.iter() {
- if tree.as_ref(ctx).contains_path(&path) {
+ if tree.read(ctx).contains_path(&path) {
return;
}
}
@@ -200,23 +200,22 @@ impl Entity for Workspace {
#[cfg(test)]
pub trait WorkspaceHandle {
- fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)>;
+ fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)>;
}
#[cfg(test)]
impl WorkspaceHandle for ModelHandle<Workspace> {
- fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)> {
- self.read(&app, |w, app| {
- w.worktrees()
- .iter()
- .flat_map(|tree| {
- let tree_id = tree.id();
- tree.as_ref(app)
- .files()
- .map(move |file| (tree_id, file.entry_id))
- })
- .collect::<Vec<_>>()
- })
+ fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)> {
+ self.read(app)
+ .worktrees()
+ .iter()
+ .flat_map(|tree| {
+ let tree_id = tree.id();
+ tree.read(app)
+ .files()
+ .map(move |file| (tree_id, file.entry_id))
+ })
+ .collect::<Vec<_>>()
}
}
@@ -228,8 +227,8 @@ mod tests {
use serde_json::json;
#[test]
- fn test_open_entry() -> Result<(), Arc<anyhow::Error>> {
- App::test((), |mut app| async move {
+ fn test_open_entry() {
+ App::test_async((), |mut app| async move {
let dir = temp_tree(json!({
"a": {
"aa": "aa contents",
@@ -241,11 +240,9 @@ mod tests {
app.finish_pending_tasks().await; // Open and populate worktree.
// Get the first file entry.
- let entry = workspace.read(&app, |w, app| {
- let tree = w.worktrees.iter().next().unwrap();
- let entry_id = tree.as_ref(app).files().next().unwrap().entry_id;
- (tree.id(), entry_id)
- });
+ let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
+ let entry_id = app.read(|ctx| tree.read(ctx).files().next().unwrap().entry_id);
+ let entry = (tree.id(), entry_id);
// Open the same entry twice before it finishes loading.
let (future_1, future_2) = workspace.update(&mut app, |w, app| {
@@ -255,18 +252,17 @@ mod tests {
)
});
- let handle_1 = future_1.await?;
- let handle_2 = future_2.await?;
+ let handle_1 = future_1.await.unwrap();
+ let handle_2 = future_2.await.unwrap();
assert_eq!(handle_1.id(), handle_2.id());
// Open the same entry again now that it has loaded
let handle_3 = workspace
.update(&mut app, |w, app| w.open_entry(entry, app).unwrap())
- .await?;
+ .await
+ .unwrap();
assert_eq!(handle_3.id(), handle_1.id());
-
- Ok(())
})
}
}
@@ -2,13 +2,13 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
use crate::{settings::Settings, watch};
use futures_core::future::LocalBoxFuture;
use gpui::{
- color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App,
- AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
+ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
+ Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
};
use log::{error, info};
use std::{collections::HashSet, path::PathBuf};
-pub fn init(app: &mut App) {
+pub fn init(app: &mut MutableAppContext) {
app.add_action("workspace:save", WorkspaceView::save_active_item);
app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
app.add_bindings(vec![
@@ -54,11 +54,11 @@ pub trait ItemViewHandle: Send + Sync {
impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
fn title(&self, app: &AppContext) -> String {
- self.as_ref(app).title(app)
+ self.read(app).title(app)
}
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
- self.as_ref(app).entry_id(app)
+ self.read(app).entry_id(app)
}
fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
@@ -93,7 +93,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
- self.as_ref(ctx).is_dirty(ctx)
+ self.read(ctx).is_dirty(ctx)
}
fn id(&self) -> usize {
@@ -154,7 +154,7 @@ impl WorkspaceView {
}
pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool {
- self.workspace.as_ref(app).contains_paths(paths, app)
+ self.workspace.read(app).contains_paths(paths, app)
}
pub fn open_paths(&self, paths: &[PathBuf], app: &mut MutableAppContext) {
@@ -228,8 +228,8 @@ impl WorkspaceView {
}
pub fn open_example_entry(&mut self, ctx: &mut ViewContext<Self>) {
- if let Some(tree) = self.workspace.as_ref(ctx).worktrees().iter().next() {
- if let Some(file) = tree.as_ref(ctx).files().next() {
+ if let Some(tree) = self.workspace.read(ctx).worktrees().iter().next() {
+ if let Some(file) = tree.read(ctx).files().next() {
info!("open_entry ({}, {})", tree.id(), file.entry_id);
self.open_entry((tree.id(), file.entry_id), ctx);
} else {
@@ -322,7 +322,7 @@ impl WorkspaceView {
) -> ViewHandle<Pane> {
let new_pane = self.add_pane(ctx);
self.activate_pane(new_pane.clone(), ctx);
- if let Some(item) = pane.as_ref(ctx).active_item() {
+ if let Some(item) = pane.read(ctx).active_item() {
if let Some(clone) = item.clone_on_split(ctx.app_mut()) {
self.add_item(clone, ctx);
}
@@ -389,13 +389,12 @@ impl View for WorkspaceView {
mod tests {
use super::{pane, Workspace, WorkspaceView};
use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _};
- use anyhow::Result;
use gpui::App;
use serde_json::json;
#[test]
- fn test_open_entry() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_open_entry() {
+ App::test_async((), |mut app| async move {
let dir = temp_tree(json!({
"a": {
"aa": "aa contents",
@@ -407,7 +406,7 @@ mod tests {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
app.finish_pending_tasks().await; // Open and populate worktree.
- let entries = workspace.file_entries(&app);
+ let entries = app.read(|ctx| workspace.file_entries(ctx));
let (_, workspace_view) =
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
@@ -416,19 +415,27 @@ mod tests {
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await;
- workspace_view.read(&app, |w, app| {
- assert_eq!(w.active_pane().as_ref(app).items().len(), 1);
+ app.read(|ctx| {
+ assert_eq!(
+ workspace_view
+ .read(ctx)
+ .active_pane()
+ .read(ctx)
+ .items()
+ .len(),
+ 1
+ )
});
// Open the second entry
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx));
app.finish_pending_tasks().await;
- workspace_view.read(&app, |w, app| {
- let active_pane = w.active_pane().as_ref(app);
+ app.read(|ctx| {
+ let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
assert_eq!(active_pane.items().len(), 2);
assert_eq!(
- active_pane.active_item().unwrap().entry_id(app),
+ active_pane.active_item().unwrap().entry_id(ctx),
Some(entries[1])
);
});
@@ -437,11 +444,11 @@ mod tests {
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await;
- workspace_view.read(&app, |w, app| {
- let active_pane = w.active_pane().as_ref(app);
+ app.read(|ctx| {
+ let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
assert_eq!(active_pane.items().len(), 2);
assert_eq!(
- active_pane.active_item().unwrap().entry_id(app),
+ active_pane.active_item().unwrap().entry_id(ctx),
Some(entries[0])
);
});
@@ -453,18 +460,24 @@ mod tests {
});
app.finish_pending_tasks().await;
- workspace_view.read(&app, |w, app| {
- assert_eq!(w.active_pane().as_ref(app).items().len(), 3);
+ app.read(|ctx| {
+ assert_eq!(
+ workspace_view
+ .read(ctx)
+ .active_pane()
+ .read(ctx)
+ .items()
+ .len(),
+ 3
+ );
});
-
- Ok(())
- })
+ });
}
#[test]
- fn test_pane_actions() -> Result<()> {
- App::test((), |mut app| async move {
- pane::init(&mut app);
+ fn test_pane_actions() {
+ App::test_async((), |mut app| async move {
+ app.update(|ctx| pane::init(ctx));
let dir = temp_tree(json!({
"a": {
@@ -477,7 +490,7 @@ mod tests {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
app.finish_pending_tasks().await; // Open and populate worktree.
- let entries = workspace.file_entries(&app);
+ let entries = app.read(|ctx| workspace.file_entries(ctx));
let (window_id, workspace_view) =
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
@@ -485,24 +498,28 @@ mod tests {
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await;
- let pane_1 = workspace_view.read(&app, |w, _| w.active_pane().clone());
+ let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
- let pane_2 = workspace_view.read(&app, |w, _| w.active_pane().clone());
- assert_ne!(pane_1, pane_2);
+ app.update(|ctx| {
+ let pane_2 = workspace_view.read(ctx).active_pane().clone();
+ assert_ne!(pane_1, pane_2);
- pane_2.read(&app, |p, app| {
- assert_eq!(p.active_item().unwrap().entry_id(app), Some(entries[0]));
- });
+ assert_eq!(
+ pane_2
+ .read(ctx)
+ .active_item()
+ .unwrap()
+ .entry_id(ctx.as_ref()),
+ Some(entries[0])
+ );
- app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
+ ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
- workspace_view.read(&app, |w, _| {
+ let w = workspace_view.read(ctx);
assert_eq!(w.panes.len(), 1);
- assert_eq!(w.active_pane(), &pane_1)
+ assert_eq!(w.active_pane(), &pane_1);
});
-
- Ok(())
- })
+ });
}
}
@@ -409,7 +409,7 @@ pub trait WorktreeHandle {
impl WorktreeHandle for ModelHandle<Worktree> {
fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> {
- if entry_id >= self.as_ref(app).entry_count() {
+ if entry_id >= self.read(app).entry_count() {
return Err(anyhow!("Entry does not exist in tree"));
}
@@ -461,15 +461,15 @@ pub struct FileHandle {
impl FileHandle {
pub fn path(&self, app: &AppContext) -> PathBuf {
- self.worktree.as_ref(app).entry_path(self.entry_id).unwrap()
+ self.worktree.read(app).entry_path(self.entry_id).unwrap()
}
pub fn load_history(&self, app: &AppContext) -> impl Future<Output = Result<History>> {
- self.worktree.as_ref(app).load_history(self.entry_id)
+ self.worktree.read(app).load_history(self.entry_id)
}
pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
- let worktree = self.worktree.as_ref(ctx);
+ let worktree = self.worktree.read(ctx);
worktree.save(self.entry_id, content, ctx)
}
@@ -648,8 +648,8 @@ mod test {
use std::os::unix;
#[test]
- fn test_populate_and_search() -> Result<()> {
- App::test((), |mut app| async move {
+ fn test_populate_and_search() {
+ App::test_async((), |mut app| async move {
let dir = temp_tree(json!({
"root": {
"apple": "",
@@ -666,12 +666,13 @@ mod test {
}));
let root_link_path = dir.path().join("root_link");
- unix::fs::symlink(&dir.path().join("root"), &root_link_path)?;
+ unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx)));
app.finish_pending_tasks().await;
- tree.read(&app, |tree, _| {
+ app.read(|ctx| {
+ let tree = tree.read(ctx);
assert_eq!(tree.file_count(), 4);
let results = match_paths(&[tree.clone()], "bna", false, false, 10)
.iter()
@@ -685,14 +686,13 @@ mod test {
PathBuf::from("root_link/banana/carrot/endive"),
]
);
- });
- Ok(())
- })
+ })
+ });
}
#[test]
fn test_save_file() {
- App::test((), |mut app| async move {
+ App::test_async((), |mut app| async move {
let dir = temp_tree(json!({
"file1": "the old contents",
}));
@@ -700,24 +700,24 @@ mod test {
let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx)));
app.finish_pending_tasks().await;
- let file_id = tree.read(&app, |tree, _| {
- let entry = tree.files().next().unwrap();
+ let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
+
+ let entry = app.read(|ctx| {
+ let entry = tree.read(ctx).files().next().unwrap();
assert_eq!(entry.path.file_name().unwrap(), "file1");
- entry.entry_id
+ entry
});
-
- let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
+ let file_id = entry.entry_id;
tree.update(&mut app, |tree, ctx| {
smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap()
});
- let history = tree
- .read(&app, |tree, _| tree.load_history(file_id))
+ let history = app
+ .read(|ctx| tree.read(ctx).load_history(file_id))
.await
.unwrap();
-
assert_eq!(history.base_text.as_ref(), buffer.text());
- })
+ });
}
}