Get app running and test passing after gpui App+Platform restructure

This commit is contained in:
Max Brunsfeld 2021-04-09 15:58:19 -07:00
parent 4ecc17b1bb
commit 079050541f
17 changed files with 719 additions and 749 deletions

View file

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

View file

@ -2,7 +2,7 @@ use crate::{
elements::ElementBox, elements::ElementBox,
executor, executor,
keymap::{self, Keystroke}, keymap::{self, Keystroke},
platform::{self, Platform as _, WindowOptions}, platform::{self, WindowOptions},
presenter::Presenter, presenter::Presenter,
util::post_inc, util::post_inc,
AssetCache, AssetSource, FontCache, TextLayoutCache, AssetCache, AssetSource, FontCache, TextLayoutCache,
@ -84,33 +84,10 @@ pub enum MenuItem<'a> {
#[derive(Clone)] #[derive(Clone)]
pub struct App(Rc<RefCell<MutableAppContext>>); pub struct App(Rc<RefCell<MutableAppContext>>);
pub trait TestClosure<'a, T> {
type Result: 'a + Future<Output = T>;
fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result;
}
impl<'a, F, R, T> TestClosure<'a, T> for F
where
F: FnOnce(&mut MutableAppContext) -> R,
R: 'a + Future<Output = T>,
{
type Result = R;
fn run_test(self, ctx: &'a mut MutableAppContext) -> Self::Result {
(self)(ctx)
}
}
impl App { impl App {
pub fn test< pub fn test<T, A: AssetSource, F: FnOnce(&mut MutableAppContext) -> T>(
T,
A: AssetSource,
// F: 'static + ,
// G: for<'a> FnOnce(&'a mut MutableAppContext) -> impl Future<Output = T>,
G: for<'a> TestClosure<'a, T>,
>(
asset_source: A, asset_source: A,
f: G, f: F,
) -> T { ) -> T {
let platform = platform::test::platform(); let platform = platform::test::platform();
let foreground = Rc::new(executor::Foreground::test()); let foreground = Rc::new(executor::Foreground::test());
@ -121,11 +98,35 @@ impl App {
))); )));
ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx)); ctx.borrow_mut().weak_self = Some(Rc::downgrade(&ctx));
let mut ctx = ctx.borrow_mut(); let mut ctx = ctx.borrow_mut();
smol::block_on(foreground.run(f.run_test(&mut *ctx))) f(&mut *ctx)
}
pub fn test_async<'a, T, F, A: AssetSource, Fn>(asset_source: A, f: Fn) -> T
where
Fn: FnOnce(&'a mut MutableAppContext) -> F,
F: Future<Output = T> + 'a,
{
let platform = platform::test::platform();
let foreground = Rc::new(executor::Foreground::test());
let ctx = Rc::new(RefCell::new(MutableAppContext::new(
foreground.clone(),
Arc::new(platform),
asset_source,
)));
let mut ctx_ref = ctx.borrow_mut();
ctx_ref.weak_self = Some(Rc::downgrade(&ctx));
let ctx = &mut *ctx_ref;
// TODO - is there a better way of getting this to compile?
let ctx = unsafe { std::mem::transmute(ctx) };
let future = f(ctx);
drop(ctx_ref);
smol::block_on(foreground.run(future))
} }
pub fn new(asset_source: impl AssetSource) -> Result<Self> { 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 foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
let app = Self(Rc::new(RefCell::new(MutableAppContext::new( let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
foreground, foreground,
@ -199,7 +200,7 @@ impl App {
self self
} }
pub fn run<F>(self, callback: F) pub fn on_finish_launching<F>(self, callback: F) -> Self
where where
F: 'static + FnOnce(&mut MutableAppContext), F: 'static + FnOnce(&mut MutableAppContext),
{ {
@ -207,7 +208,16 @@ impl App {
self.0 self.0
.borrow() .borrow()
.platform .platform
.run(Box::new(move || callback(&mut *ctx.borrow_mut()))); .on_finish_launching(Box::new(move || callback(&mut *ctx.borrow_mut())));
self
}
pub fn set_menus(&self, menus: &[Menu]) {
self.0.borrow().platform.set_menus(menus);
}
pub fn run(self) {
platform::current::run();
} }
pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>( pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>(
@ -246,7 +256,7 @@ impl App {
name: &str, name: &str,
arg: T, arg: T,
) { ) {
self.0.borrow_mut().dispatch_action( self.0.borrow_mut().dispatch_action_any(
window_id, window_id,
&responder_chain, &responder_chain,
name, name,
@ -280,15 +290,6 @@ impl App {
handle 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>) pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
where where
T: View, T: View,
@ -345,15 +346,6 @@ impl App {
result 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 = ()> { pub fn finish_pending_tasks(&self) -> impl Future<Output = ()> {
self.0.borrow().finish_pending_tasks() self.0.borrow().finish_pending_tasks()
} }
@ -478,6 +470,10 @@ impl MutableAppContext {
self.platform.clone() self.platform.clone()
} }
pub fn font_cache(&self) -> &Arc<FontCache> {
&self.font_cache
}
pub fn foreground_executor(&self) -> &Rc<executor::Foreground> { pub fn foreground_executor(&self) -> &Rc<executor::Foreground> {
&self.foreground &self.foreground
} }
@ -598,7 +594,24 @@ impl MutableAppContext {
self.ctx.render_views(window_id) 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, &mut self,
window_id: usize, window_id: usize,
path: &[usize], path: &[usize],
@ -709,7 +722,7 @@ impl MutableAppContext {
MatchResult::None => {} MatchResult::None => {}
MatchResult::Pending => pending = true, MatchResult::Pending => pending = true,
MatchResult::Action { name, arg } => { MatchResult::Action { name, arg } => {
if self.dispatch_action( if self.dispatch_action_any(
window_id, window_id,
&responder_chain[0..=i], &responder_chain[0..=i],
&name, &name,
@ -796,7 +809,7 @@ impl MutableAppContext {
.borrow_mut() .borrow_mut()
.dispatch_event(event, ctx.downgrade()); .dispatch_event(event, ctx.downgrade());
for action in actions { for action in actions {
ctx.dispatch_action( ctx.dispatch_action_any(
window_id, window_id,
&action.path, &action.path,
action.name, action.name,
@ -1328,6 +1341,12 @@ impl UpdateView for MutableAppContext {
} }
} }
impl AsRef<AppContext> for MutableAppContext {
fn as_ref(&self) -> &AppContext {
&self.ctx
}
}
pub struct AppContext { pub struct AppContext {
models: HashMap<usize, Box<dyn AnyModel>>, models: HashMap<usize, Box<dyn AnyModel>>,
windows: HashMap<usize, Window>, windows: HashMap<usize, Window>,
@ -2112,13 +2131,6 @@ impl<T: View> ViewHandle<T> {
app.view(self) app.view(self)
} }
pub fn read<'a, F, S>(&self, app: &'a App, read: F) -> S
where
F: FnOnce(&T, &AppContext) -> S,
{
app.read_view(self, read)
}
pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
where where
A: UpdateView, A: UpdateView,
@ -2452,10 +2464,10 @@ mod tests {
} }
} }
App::test((), |app: &mut MutableAppContext| async move { App::test((), |app| {
let handle_1 = app.add_model(|ctx| Model::new(None, ctx)); let handle_1 = app.add_model(|ctx| Model::new(None, ctx));
let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), 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| { handle_1.update(app, |model, ctx| {
model.events.push("updated".into()); model.events.push("updated".into());
@ -2478,11 +2490,10 @@ mod tests {
model.other.take(); model.other.take();
}); });
let app_state = app.0.borrow(); assert_eq!(app.ctx.models.len(), 1);
assert_eq!(app_state.ctx.models.len(), 1); assert!(app.subscriptions.is_empty());
assert!(app_state.subscriptions.is_empty()); assert!(app.observations.is_empty());
assert!(app_state.observations.is_empty()); });
})
} }
#[test] #[test]
@ -2496,8 +2507,7 @@ mod tests {
type Event = usize; type Event = usize;
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let handle_1 = app.add_model(|_| Model::default()); let handle_1 = app.add_model(|_| Model::default());
let handle_2 = app.add_model(|_| Model::default()); let handle_2 = app.add_model(|_| Model::default());
let handle_2b = handle_2.clone(); let handle_2b = handle_2.clone();
@ -2513,10 +2523,10 @@ mod tests {
}); });
handle_2.update(app, |_, c| c.emit(7)); handle_2.update(app, |_, c| c.emit(7));
handle_1.read(app, |model, _| assert_eq!(model.events, vec![7])); assert_eq!(handle_1.as_ref(app).events, vec![7]);
handle_2.update(app, |_, c| c.emit(5)); handle_2.update(app, |_, c| c.emit(5));
handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5])); assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5]);
}) })
} }
@ -2532,8 +2542,7 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let handle_1 = app.add_model(|_| Model::default()); let handle_1 = app.add_model(|_| Model::default());
let handle_2 = app.add_model(|_| Model::default()); let handle_2 = app.add_model(|_| Model::default());
let handle_2b = handle_2.clone(); let handle_2b = handle_2.clone();
@ -2551,13 +2560,13 @@ mod tests {
model.count = 7; model.count = 7;
c.notify() c.notify()
}); });
handle_1.read(app, |model, _| assert_eq!(model.events, vec![7])); assert_eq!(handle_1.as_ref(app).events, vec![7]);
handle_2.update(app, |model, c| { handle_2.update(app, |model, c| {
model.count = 5; model.count = 5;
c.notify() c.notify()
}); });
handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5])) assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5])
}) })
} }
@ -2572,25 +2581,25 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test_async((), |app| async move {
let handle = app.add_model(|_| Model::default()); let handle = app.add_model(|_| Model::default());
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn(async { 7 }, |model, output, _| { c.spawn(async { 7 }, |model, output, _| {
model.count = output; model.count = output;
}) })
}) })
.await; .await;
handle.read(&app, |model, _| assert_eq!(model.count, 7)); assert_eq!(handle.as_ref(app).count, 7);
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn(async { 14 }, |model, output, _| { c.spawn(async { 14 }, |model, output, _| {
model.count = output; model.count = output;
}) })
}) })
.await; .await;
handle.read(&app, |model, _| assert_eq!(model.count, 14)); assert_eq!(handle.as_ref(app).count, 14);
}); });
} }
@ -2605,10 +2614,10 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test_async((), |app| async move {
let handle = app.add_model(|_| Model::default()); let handle = app.add_model(|_| Model::default());
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn_stream( c.spawn_stream(
smol::stream::iter(vec![1, 2, 3]), smol::stream::iter(vec![1, 2, 3]),
|model, output, _| { |model, output, _| {
@ -2620,10 +2629,7 @@ mod tests {
) )
}) })
.await; .await;
assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None])
handle.read(&app, |model, _| {
assert_eq!(model.events, [Some(1), Some(2), Some(3), None])
});
}) })
} }
@ -2662,40 +2668,34 @@ mod tests {
} }
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (window_id, _) = app.add_window(|ctx| View::new(None, ctx)); 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_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)); 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| { handle_1.update(app, |view, ctx| {
view.events.push("updated".into()); view.events.push("updated".into());
ctx.emit(1); ctx.emit(1);
ctx.emit(2); ctx.emit(2);
}); });
handle_1.read(app, |view, _| { assert_eq!(handle_1.as_ref(app).events, vec!["updated".to_string()]);
assert_eq!(view.events, vec!["updated".to_string()]); assert_eq!(
}); handle_2.as_ref(app).events,
handle_2.read(app, |view, _| { vec![
assert_eq!( "observed event 1".to_string(),
view.events, "observed event 2".to_string(),
vec![ ]
"observed event 1".to_string(), );
"observed event 2".to_string(),
]
);
});
handle_2.update(app, |view, _| { handle_2.update(app, |view, _| {
drop(handle_1); drop(handle_1);
view.other.take(); view.other.take();
}); });
let app_state = app.0.borrow(); assert_eq!(app.ctx.windows[&window_id].views.len(), 2);
assert_eq!(app_state.ctx.windows[&window_id].views.len(), 2); assert!(app.subscriptions.is_empty());
assert!(app_state.subscriptions.is_empty()); assert!(app.observations.is_empty());
assert!(app_state.observations.is_empty());
}) })
} }
@ -2726,8 +2726,7 @@ mod tests {
type Event = usize; type Event = usize;
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (window_id, handle_1) = app.add_window(|_| View::default()); let (window_id, handle_1) = app.add_window(|_| View::default());
let handle_2 = app.add_view(window_id, |_| View::default()); let handle_2 = app.add_view(window_id, |_| View::default());
let handle_2b = handle_2.clone(); let handle_2b = handle_2.clone();
@ -2748,13 +2747,13 @@ mod tests {
}); });
handle_2.update(app, |_, c| c.emit(7)); handle_2.update(app, |_, c| c.emit(7));
handle_1.read(app, |view, _| assert_eq!(view.events, vec![7])); assert_eq!(handle_1.as_ref(app).events, vec![7]);
handle_2.update(app, |_, c| c.emit(5)); handle_2.update(app, |_, c| c.emit(5));
handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5])); assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5]);
handle_3.update(app, |_, c| c.emit(9)); handle_3.update(app, |_, c| c.emit(9));
handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5, 9])); assert_eq!(handle_1.as_ref(app).events, vec![7, 10, 5, 9]);
}) })
} }
@ -2782,9 +2781,7 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (window_id, _) = app.add_window(|_| View); let (window_id, _) = app.add_window(|_| View);
let observing_view = app.add_view(window_id, |_| View); let observing_view = app.add_view(window_id, |_| View);
let emitting_view = app.add_view(window_id, |_| View); let emitting_view = app.add_view(window_id, |_| View);
@ -2799,7 +2796,7 @@ mod tests {
ctx.subscribe(&observed_model, |_, _, _| {}); ctx.subscribe(&observed_model, |_, _, _| {});
}); });
app.update(|_| { app.update(|| {
drop(observing_view); drop(observing_view);
drop(observing_model); drop(observing_model);
}); });
@ -2839,8 +2836,7 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (_, view) = app.add_window(|_| View::default()); let (_, view) = app.add_window(|_| View::default());
let model = app.add_model(|_| Model::default()); let model = app.add_model(|_| Model::default());
@ -2854,7 +2850,7 @@ mod tests {
model.count = 11; model.count = 11;
c.notify(); c.notify();
}); });
view.read(app, |view, _| assert_eq!(view.events, vec![11])); assert_eq!(view.as_ref(app).events, vec![11]);
}) })
} }
@ -2882,9 +2878,7 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (window_id, _) = app.add_window(|_| View); let (window_id, _) = app.add_window(|_| View);
let observing_view = app.add_view(window_id, |_| View); let observing_view = app.add_view(window_id, |_| View);
let observing_model = app.add_model(|_| Model); let observing_model = app.add_model(|_| Model);
@ -2897,7 +2891,7 @@ mod tests {
ctx.observe(&observed_model, |_, _, _| {}); ctx.observe(&observed_model, |_, _, _| {});
}); });
app.update(|_| { app.update(|| {
drop(observing_view); drop(observing_view);
drop(observing_model); drop(observing_model);
}); });
@ -2937,8 +2931,7 @@ mod tests {
} }
} }
App::test((), |mut app| async move { App::test((), |app| {
let app = &mut app;
let (window_id, view_1) = app.add_window(|_| View::default()); let (window_id, view_1) = app.add_window(|_| View::default());
let view_2 = app.add_view(window_id, |_| View::default()); let view_2 = app.add_view(window_id, |_| View::default());
@ -2953,18 +2946,16 @@ mod tests {
ctx.focus(&view_1); ctx.focus(&view_1);
}); });
view_1.read(app, |view_1, _| { assert_eq!(
assert_eq!( view_1.as_ref(app).events,
view_1.events, [
[ "self focused".to_string(),
"self focused".to_string(), "self blurred".to_string(),
"self blurred".to_string(), "view 2 focused".to_string(),
"view 2 focused".to_string(), "self focused".to_string(),
"self focused".to_string(), "view 2 blurred".to_string(),
"view 2 blurred".to_string(), ],
], );
);
});
}) })
} }
@ -2989,24 +2980,24 @@ mod tests {
} }
} }
App::test((), |mut app| async move { App::test_async((), |app| async move {
let (_, handle) = app.add_window(|_| View::default()); let (_, handle) = app.add_window(|_| View::default());
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn(async { 7 }, |me, output, _| { c.spawn(async { 7 }, |me, output, _| {
me.count = output; me.count = output;
}) })
}) })
.await; .await;
handle.read(&app, |view, _| assert_eq!(view.count, 7)); assert_eq!(handle.as_ref(app).count, 7);
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn(async { 14 }, |me, output, _| { c.spawn(async { 14 }, |me, output, _| {
me.count = output; me.count = output;
}) })
}) })
.await; .await;
handle.read(&app, |view, _| assert_eq!(view.count, 14)); assert_eq!(handle.as_ref(app).count, 14);
}); });
} }
@ -3031,10 +3022,10 @@ mod tests {
} }
} }
App::test((), |mut app| async move { App::test_async((), |app| async move {
let (_, handle) = app.add_window(|_| View::default()); let (_, handle) = app.add_window(|_| View::default());
handle handle
.update(&mut app, |_, c| { .update(app, |_, c| {
c.spawn_stream( c.spawn_stream(
smol::stream::iter(vec![1_usize, 2, 3]), smol::stream::iter(vec![1_usize, 2, 3]),
|me, output, _| { |me, output, _| {
@ -3047,9 +3038,7 @@ mod tests {
}) })
.await; .await;
handle.read(&app, |view, _| { assert_eq!(handle.as_ref(app).events, [Some(1), Some(2), Some(3), None])
assert_eq!(view.events, [Some(1), Some(2), Some(3), None])
});
}); });
} }
@ -3095,7 +3084,7 @@ mod tests {
foo: String, foo: String,
} }
App::test((), |mut app| async move { App::test((), |app| {
let actions = Rc::new(RefCell::new(Vec::new())); let actions = Rc::new(RefCell::new(Vec::new()));
let actions_clone = actions.clone(); let actions_clone = actions.clone();
@ -3169,7 +3158,7 @@ mod tests {
} }
#[test] #[test]
fn test_dispatch_keystroke() -> Result<()> { fn test_dispatch_keystroke() {
use std::cell::Cell; use std::cell::Cell;
#[derive(Clone)] #[derive(Clone)]
@ -3209,7 +3198,7 @@ mod tests {
} }
} }
App::test((), |mut app| async move { App::test((), |app| {
let mut view_1 = View::new(1); let mut view_1 = View::new(1);
let mut view_2 = View::new(2); let mut view_2 = View::new(2);
let mut view_3 = View::new(3); let mut view_3 = View::new(3);
@ -3238,12 +3227,12 @@ mod tests {
app.dispatch_keystroke( app.dispatch_keystroke(
window_id, window_id,
vec![view_1.id(), view_2.id(), view_3.id()], vec![view_1.id(), view_2.id(), view_3.id()],
&Keystroke::parse("a")?, &Keystroke::parse("a").unwrap(),
)?; )
.unwrap();
assert!(handled_action.get()); assert!(handled_action.get());
Ok(()) });
})
} }
// #[test] // #[test]
@ -3266,7 +3255,7 @@ mod tests {
// } // }
// } // }
// App::test(|mut app| async move { // App::test(|app| async move {
// let (window_id, _) = app.add_window(|_| View { count: 3 }); // let (window_id, _) = app.add_window(|_| View { count: 3 });
// let view_1 = app.add_view(window_id, |_| View { count: 1 }); // let view_1 = app.add_view(window_id, |_| View { count: 1 });
// let view_2 = app.add_view(window_id, |_| View { count: 2 }); // let view_2 = app.add_view(window_id, |_| View { count: 2 });
@ -3293,7 +3282,7 @@ mod tests {
// }); // });
// let view_2_id = view_2.id(); // let view_2_id = view_2.id();
// view_1.update(&mut app, |view, ctx| { // view_1.update(app, |view, ctx| {
// view.count = 7; // view.count = 7;
// ctx.notify(); // ctx.notify();
// drop(view_2); // drop(view_2);
@ -3304,7 +3293,7 @@ mod tests {
// assert!(invalidation.updated.contains(&view_1.id())); // assert!(invalidation.updated.contains(&view_1.id()));
// assert_eq!(invalidation.removed, vec![view_2_id]); // assert_eq!(invalidation.removed, vec![view_2_id]);
// let view_3 = view_1.update(&mut app, |_, ctx| ctx.add_view(|_| View { count: 8 })); // let view_3 = view_1.update(app, |_, ctx| ctx.add_view(|_| View { count: 8 }));
// let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap(); // let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
// assert_eq!(invalidation.updated.len(), 1); // assert_eq!(invalidation.updated.len(), 1);
@ -3312,7 +3301,7 @@ mod tests {
// assert!(invalidation.removed.is_empty()); // assert!(invalidation.removed.is_empty());
// view_3 // view_3
// .update(&mut app, |_, ctx| { // .update(app, |_, ctx| {
// ctx.spawn_local(async { 9 }, |me, output, ctx| { // ctx.spawn_local(async { 9 }, |me, output, ctx| {
// me.count = output; // me.count = output;
// ctx.notify(); // ctx.notify();
@ -3351,49 +3340,49 @@ mod tests {
type Event = (); type Event = ();
} }
App::test((), |mut app| async move { App::test_async((), |app| async move {
let model = app.add_model(|_| Model); let model = app.add_model(|_| Model);
let (_, view) = app.add_window(|_| View); let (_, view) = app.add_window(|_| View);
model.update(&mut app, |_, ctx| { model.update(app, |_, ctx| {
ctx.spawn(async {}, |_, _, _| {}).detach(); ctx.spawn(async {}, |_, _, _| {}).detach();
// Cancel this task // Cancel this task
drop(ctx.spawn(async {}, |_, _, _| {})); drop(ctx.spawn(async {}, |_, _, _| {}));
}); });
view.update(&mut app, |_, ctx| { view.update(app, |_, ctx| {
ctx.spawn(async {}, |_, _, _| {}).detach(); ctx.spawn(async {}, |_, _, _| {}).detach();
// Cancel this task // Cancel this task
drop(ctx.spawn(async {}, |_, _, _| {})); drop(ctx.spawn(async {}, |_, _, _| {}));
}); });
assert!(!app.0.borrow().future_handlers.borrow().is_empty()); assert!(!app.future_handlers.borrow().is_empty());
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
assert!(app.0.borrow().future_handlers.borrow().is_empty()); assert!(app.future_handlers.borrow().is_empty());
app.finish_pending_tasks().await; // Don't block if there are no tasks app.finish_pending_tasks().await; // Don't block if there are no tasks
model.update(&mut app, |_, ctx| { model.update(app, |_, ctx| {
ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})
.detach(); .detach();
// Cancel this task // Cancel this task
drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}));
}); });
view.update(&mut app, |_, ctx| { view.update(app, |_, ctx| {
ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}) ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})
.detach(); .detach();
// Cancel this task // Cancel this task
drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {})); drop(ctx.spawn_stream(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}, |_, _| {}));
}); });
assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); assert!(!app.stream_handlers.borrow().is_empty());
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
assert!(app.0.borrow().stream_handlers.borrow().is_empty()); assert!(app.stream_handlers.borrow().is_empty());
app.finish_pending_tasks().await; // Don't block if there are no tasks app.finish_pending_tasks().await; // Don't block if there are no tasks
// Tasks are considered finished when we drop handles // Tasks are considered finished when we drop handles
let mut tasks = Vec::new(); let mut tasks = Vec::new();
model.update(&mut app, |_, ctx| { model.update(app, |_, ctx| {
tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {})));
tasks.push(Box::new(ctx.spawn_stream( tasks.push(Box::new(ctx.spawn_stream(
smol::stream::iter(vec![1, 2, 3]), smol::stream::iter(vec![1, 2, 3]),
@ -3402,7 +3391,7 @@ mod tests {
))); )));
}); });
view.update(&mut app, |_, ctx| { view.update(app, |_, ctx| {
tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {}))); tasks.push(Box::new(ctx.spawn(async {}, |_, _, _| {})));
tasks.push(Box::new(ctx.spawn_stream( tasks.push(Box::new(ctx.spawn_stream(
smol::stream::iter(vec![1, 2, 3]), smol::stream::iter(vec![1, 2, 3]),
@ -3411,12 +3400,12 @@ mod tests {
))); )));
}); });
assert!(!app.0.borrow().stream_handlers.borrow().is_empty()); assert!(!app.stream_handlers.borrow().is_empty());
let finish_pending_tasks = app.finish_pending_tasks(); let finish_pending_tasks = app.finish_pending_tasks();
drop(tasks); drop(tasks);
finish_pending_tasks.await; finish_pending_tasks.await;
assert!(app.0.borrow().stream_handlers.borrow().is_empty()); assert!(app.stream_handlers.borrow().is_empty());
app.finish_pending_tasks().await; // Don't block if there are no tasks app.finish_pending_tasks().await; // Don't block if there are no tasks
}); });
} }

View file

@ -5,7 +5,6 @@ mod fonts;
mod geometry; mod geometry;
mod platform; mod platform;
mod renderer; mod renderer;
mod runner;
mod sprite_cache; mod sprite_cache;
mod window; mod window;
@ -13,15 +12,15 @@ use cocoa::base::{BOOL, NO, YES};
pub use dispatcher::Dispatcher; pub use dispatcher::Dispatcher;
pub use fonts::FontSystem; pub use fonts::FontSystem;
use platform::MacPlatform; use platform::MacPlatform;
pub use runner::Runner; use std::sync::Arc;
use window::Window; use window::Window;
pub fn app() -> impl super::Platform { pub fn platform() -> Arc<dyn super::Platform> {
MacPlatform::new() MacPlatform::new()
} }
pub fn runner() -> impl super::Runner { pub fn run() {
Runner::new() MacPlatform::run();
} }
trait BoolExt { trait BoolExt {

View file

@ -90,13 +90,112 @@ struct Callbacks {
} }
impl MacPlatform { impl MacPlatform {
pub fn new() -> Self { pub fn new() -> Arc<dyn platform::Platform> {
Self { let result = Arc::new(Self {
dispatcher: Arc::new(Dispatcher), dispatcher: Arc::new(Dispatcher),
fonts: Arc::new(FontSystem::new()), fonts: Arc::new(FontSystem::new()),
callbacks: Default::default(), callbacks: Default::default(),
menu_item_actions: Default::default(), menu_item_actions: Default::default(),
});
unsafe {
let app: id = msg_send![APP_CLASS, sharedApplication];
let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
let self_ptr = result.as_ref() as *const Self as *const c_void;
app.setDelegate_(app_delegate);
(*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
(*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
} }
result
}
pub fn run() {
unsafe {
let pool = NSAutoreleasePool::new(nil);
let app: id = msg_send![APP_CLASS, sharedApplication];
app.run();
pool.drain();
(*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
(*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
}
}
unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id {
let menu_bar = NSMenu::new(nil).autorelease();
let mut menu_item_actions = self.menu_item_actions.borrow_mut();
menu_item_actions.clear();
for menu_config in menus {
let menu_bar_item = NSMenuItem::new(nil).autorelease();
let menu = NSMenu::new(nil).autorelease();
menu.setTitle_(ns_string(menu_config.name));
for item_config in menu_config.items {
let item;
match item_config {
MenuItem::Separator => {
item = NSMenuItem::separatorItem(nil);
}
MenuItem::Action {
name,
keystroke,
action,
} => {
if let Some(keystroke) = keystroke {
let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
panic!(
"Invalid keystroke for menu item {}:{} - {:?}",
menu_config.name, name, err
)
});
let mut mask = NSEventModifierFlags::empty();
for (modifier, flag) in &[
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
(keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
(keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
] {
if *modifier {
mask |= *flag;
}
}
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
selector("handleGPUIMenuItem:"),
ns_string(&keystroke.key),
)
.autorelease();
item.setKeyEquivalentModifierMask_(mask);
} else {
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
selector("handleGPUIMenuItem:"),
ns_string(""),
)
.autorelease();
}
let tag = menu_item_actions.len() as NSInteger;
let _: () = msg_send![item, setTag: tag];
menu_item_actions.push(action.to_string());
}
}
menu.addItem_(item);
}
menu_bar_item.setSubmenu_(menu);
menu_bar.addItem_(menu_bar_item);
}
menu_bar
} }
} }
@ -121,23 +220,8 @@ impl platform::Platform for MacPlatform {
self.callbacks.borrow_mut().open_files = Some(callback); self.callbacks.borrow_mut().open_files = Some(callback);
} }
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>) { fn on_finish_launching(&self, callback: Box<dyn FnOnce() -> ()>) {
self.callbacks.borrow_mut().finish_launching = Some(on_finish_launching); self.callbacks.borrow_mut().finish_launching = Some(callback);
unsafe {
let pool = NSAutoreleasePool::new(nil);
let app: id = msg_send![APP_CLASS, sharedApplication];
let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
let self_ptr = self as *const Self as *mut c_void;
(*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
(*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
app.setDelegate_(app_delegate);
app.run();
pool.drain();
(*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
(*app_delegate).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
}
} }
fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> { fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
@ -222,84 +306,6 @@ impl platform::Platform for MacPlatform {
} }
} }
impl MacPlatform {
unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id {
let menu_bar = NSMenu::new(nil).autorelease();
let mut menu_item_actions = self.menu_item_actions.borrow_mut();
menu_item_actions.clear();
for menu_config in menus {
let menu_bar_item = NSMenuItem::new(nil).autorelease();
let menu = NSMenu::new(nil).autorelease();
menu.setTitle_(ns_string(menu_config.name));
for item_config in menu_config.items {
let item;
match item_config {
MenuItem::Separator => {
item = NSMenuItem::separatorItem(nil);
}
MenuItem::Action {
name,
keystroke,
action,
} => {
if let Some(keystroke) = keystroke {
let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
panic!(
"Invalid keystroke for menu item {}:{} - {:?}",
menu_config.name, name, err
)
});
let mut mask = NSEventModifierFlags::empty();
for (modifier, flag) in &[
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
(keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
(keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
] {
if *modifier {
mask |= *flag;
}
}
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
selector("handleGPUIMenuItem:"),
ns_string(&keystroke.key),
)
.autorelease();
item.setKeyEquivalentModifierMask_(mask);
} else {
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
selector("handleGPUIMenuItem:"),
ns_string(""),
)
.autorelease();
}
let tag = menu_item_actions.len() as NSInteger;
let _: () = msg_send![item, setTag: tag];
menu_item_actions.push(action.to_string());
}
}
menu.addItem_(item);
}
menu_bar_item.setSubmenu_(menu);
menu_bar.addItem_(menu_bar_item);
}
menu_bar
}
}
unsafe fn get_platform(object: &mut Object) -> &MacPlatform { unsafe fn get_platform(object: &mut Object) -> &MacPlatform {
let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR); let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
assert!(!platform_ptr.is_null()); assert!(!platform_ptr.is_null());

View file

@ -22,24 +22,13 @@ use async_task::Runnable;
pub use event::Event; pub use event::Event;
use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc}; use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc};
pub trait Runner {
fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self;
fn on_menu_command<F: 'static + FnMut(&str)>(self, callback: F) -> Self;
fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
fn set_menus(self, menus: &[Menu]) -> Self;
fn run(self);
}
pub trait Platform { pub trait Platform {
fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>); fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>);
fn on_become_active(&self, callback: Box<dyn FnMut()>); fn on_become_active(&self, callback: Box<dyn FnMut()>);
fn on_resign_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_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>); fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>); fn on_finish_launching(&self, callback: Box<dyn FnOnce() -> ()>);
fn dispatcher(&self) -> Arc<dyn Dispatcher>; fn dispatcher(&self) -> Arc<dyn Dispatcher>;
fn fonts(&self) -> Arc<dyn FontSystem>; fn fonts(&self) -> Arc<dyn FontSystem>;

View file

@ -39,9 +39,7 @@ impl super::Platform for Platform {
fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {} fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) { fn on_finish_launching(&self, _: Box<dyn FnOnce() -> ()>) {}
unimplemented!()
}
fn dispatcher(&self) -> Arc<dyn super::Dispatcher> { fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
self.dispatcher.clone() self.dispatcher.clone()
@ -61,8 +59,7 @@ impl super::Platform for Platform {
Ok(Box::new(Window::new(options.bounds.size()))) Ok(Box::new(Window::new(options.bounds.size())))
} }
fn set_menus(&self, _menus: &[crate::Menu]) { fn set_menus(&self, _menus: &[crate::Menu]) {}
}
fn quit(&self) {} fn quit(&self) {}

View file

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

View file

@ -6,7 +6,7 @@ use crate::{settings::Settings, watch, workspace};
use anyhow::Result; use anyhow::Result;
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use gpui::{ use gpui::{
fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element, fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element,
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
WeakViewHandle, WeakViewHandle,
}; };
@ -1240,112 +1240,125 @@ mod tests {
use super::*; use super::*;
use crate::{editor::Point, settings, test::sample_text}; use crate::{editor::Point, settings, test::sample_text};
use anyhow::Error; use anyhow::Error;
use gpui::App;
use unindent::Unindent; use unindent::Unindent;
#[test] #[test]
fn test_selection_with_mouse() { 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 buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, buffer_view) = let (_, buffer_view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); 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); view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] .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); view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] .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); view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] .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.end_selection(ctx);
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] .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.begin_selection(DisplayPoint::new(3, 3), true, ctx);
view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[ .collect::<Vec<_>>();
DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1), assert_eq!(
DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0) 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); view.end_selection(ctx);
}); });
buffer_view.read(&app, |view, app| { let view = buffer_view.as_ref(app);
let selections = view let selections = view
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app) .selections_in_range(
.collect::<Vec<_>>(); DisplayPoint::zero()..view.max_point(app.as_ref()),
assert_eq!( app.as_ref(),
selections, )
[DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)] .collect::<Vec<_>>();
); assert_eq!(
}); selections,
[DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
);
}); });
} }
#[test] #[test]
fn test_layout_line_numbers() -> Result<()> { fn test_layout_line_numbers() {
App::test((), |mut app| async move { App::test((), |app| {
let layout_cache = TextLayoutCache::new(app.platform().fonts()); 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))); let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
@ -1353,19 +1366,17 @@ mod tests {
let (_, view) = let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.read(&app, |view, app| { let layouts = view
let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?; .as_ref(app)
assert_eq!(layouts.len(), 6); .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
Result::<()>::Ok(()) .unwrap();
})?; assert_eq!(layouts.len(), 6);
Ok(())
}) })
} }
#[test] #[test]
fn test_fold() -> Result<()> { fn test_fold() {
App::test((), |mut app| async move { App::test((), |app| {
let buffer = app.add_model(|_| { let buffer = app.add_model(|_| {
Buffer::new( Buffer::new(
0, 0,
@ -1393,8 +1404,9 @@ mod tests {
let (_, view) = let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(&mut app, |view, ctx| { view.update(app, |view, ctx| {
view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)?; view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
.unwrap();
view.fold(&(), ctx); view.fold(&(), ctx);
assert_eq!( assert_eq!(
view.text(ctx.app()), view.text(ctx.app()),
@ -1449,23 +1461,19 @@ mod tests {
view.unfold(&(), ctx); view.unfold(&(), ctx);
assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text()); assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text());
});
Ok::<(), Error>(()) });
})?;
Ok(())
})
} }
#[test] #[test]
fn test_move_cursor() -> Result<()> { 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 buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
buffer.update(&mut app, |buffer, ctx| { buffer.update(app, |buffer, ctx| {
buffer.edit( buffer.edit(
vec![ vec![
Point::new(1, 0)..Point::new(1, 0), Point::new(1, 0)..Point::new(1, 0),
@ -1476,7 +1484,7 @@ mod tests {
) )
})?; })?;
view.update(&mut app, |view, ctx| { view.update(app, |view, ctx| {
view.move_down(&(), ctx); view.move_down(&(), ctx);
assert_eq!( assert_eq!(
view.selections(ctx.app()), view.selections(ctx.app()),
@ -1495,8 +1503,8 @@ mod tests {
} }
#[test] #[test]
fn test_backspace() -> Result<()> { fn test_backspace() {
App::test((), |mut app| async move { App::test((), |app| {
let buffer = app.add_model(|_| { let buffer = app.add_model(|_| {
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n") Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
}); });
@ -1504,7 +1512,7 @@ mod tests {
let (_, view) = let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); 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( view.select_ranges(
&[ &[
// an empty selection - the preceding character is deleted // an empty selection - the preceding character is deleted
@ -1515,17 +1523,15 @@ mod tests {
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
], ],
ctx, ctx,
)?; )
.unwrap();
view.backspace(&(), ctx); view.backspace(&(), ctx);
Ok(()) });
})?;
buffer.read(&mut app, |buffer, _| -> Result<()> { assert_eq!(
assert_eq!(buffer.text(), "oe two three\nfou five six\nseven ten\n"); buffer.as_ref(app).text(),
Ok(()) "oe two three\nfou five six\nseven ten\n"
})?; );
Ok(())
}) })
} }

View file

@ -469,115 +469,106 @@ mod tests {
use gpui::App; use gpui::App;
#[test] #[test]
fn test_basic_folds() -> Result<()> { fn test_basic_folds() {
App::test((), |mut app| async move { App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let mut map = app.read(|app| FoldMap::new(buffer.clone(), app)); let mut map = FoldMap::new(buffer.clone(), app.as_ref());
app.read(|app| { map.fold(
map.fold( vec![
vec![ Point::new(0, 2)..Point::new(2, 2),
Point::new(0, 2)..Point::new(2, 2), Point::new(2, 4)..Point::new(4, 1),
Point::new(2, 4)..Point::new(4, 1), ],
], app.as_ref(),
app, )
)?; .unwrap();
assert_eq!(map.text(app), "aa…cc…eeeee"); assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
Ok::<(), anyhow::Error>(())
})?;
let edits = buffer.update(&mut app, |buffer, ctx| { let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone(); let start_version = buffer.version.clone();
buffer.edit( buffer
vec![ .edit(
Point::new(0, 0)..Point::new(0, 1), vec![
Point::new(2, 3)..Point::new(2, 3), Point::new(0, 0)..Point::new(0, 1),
], Point::new(2, 3)..Point::new(2, 3),
"123", ],
Some(ctx), "123",
)?; Some(ctx),
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>()) )
})?; .unwrap();
buffer.edits_since(start_version).collect::<Vec<_>>()
});
app.read(|app| { map.apply_edits(&edits, app.as_ref()).unwrap();
map.apply_edits(&edits, app)?; assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
assert_eq!(map.text(app), "123a…c123c…eeeee");
Ok::<(), anyhow::Error>(())
})?;
let edits = buffer.update(&mut app, |buffer, ctx| { let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone(); let start_version = buffer.version.clone();
buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))?; buffer
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>()) .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
})?; .unwrap();
buffer.edits_since(start_version).collect::<Vec<_>>()
});
app.read(|app| { map.apply_edits(&edits, app.as_ref()).unwrap();
map.apply_edits(&edits, app)?; assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
assert_eq!(map.text(app), "123a…c123456eee");
map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?; map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee"); .unwrap();
assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
});
}
Ok(()) #[test]
}) fn test_overlapping_folds() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
vec![
Point::new(0, 2)..Point::new(2, 2),
Point::new(0, 4)..Point::new(1, 0),
Point::new(1, 2)..Point::new(3, 2),
Point::new(3, 1)..Point::new(4, 1),
],
app.as_ref(),
)
.unwrap();
assert_eq!(map.text(app.as_ref()), "aa…eeeee");
}) })
} }
#[test] #[test]
fn test_overlapping_folds() -> Result<()> { fn test_merging_folds_via_edit() {
App::test((), |mut app| async move { App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
app.read(|app| { let mut map = FoldMap::new(buffer.clone(), app.as_ref());
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(())
})
})
}
#[test] map.fold(
fn test_merging_folds_via_edit() -> Result<()> { vec![
App::test((), |mut app| async move { Point::new(0, 2)..Point::new(2, 2),
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); Point::new(3, 1)..Point::new(4, 1),
let mut map = app.read(|app| FoldMap::new(buffer.clone(), app)); ],
app.as_ref(),
)
.unwrap();
assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
app.read(|app| { let edits = buffer.update(app, |buffer, ctx| {
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 start_version = buffer.version.clone(); let start_version = buffer.version.clone();
buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))?; buffer
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>()) .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
})?; .unwrap();
buffer.edits_since(start_version).collect::<Vec<_>>()
});
app.read(|app| { map.apply_edits(&edits, app.as_ref()).unwrap();
map.apply_edits(&edits, app)?; assert_eq!(map.text(app.as_ref()), "aa…eeeee");
assert_eq!(map.text(app), "aa…eeeee"); });
Ok(())
})
})
} }
#[test] #[test]
fn test_random_folds() -> Result<()> { fn test_random_folds() {
use crate::editor::ToPoint; use crate::editor::ToPoint;
use crate::util::RandomCharIter; use crate::util::RandomCharIter;
use rand::prelude::*; use rand::prelude::*;
@ -597,15 +588,15 @@ mod tests {
println!("{:?}", seed); println!("{:?}", seed);
let mut rng = StdRng::seed_from_u64(seed); let mut rng = StdRng::seed_from_u64(seed);
App::test((), |mut app| async move { App::test((), |app| {
let buffer = app.add_model(|_| { let buffer = app.add_model(|_| {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>(); let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
Buffer::new(0, text) 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.as_ref(app);
let fold_count = rng.gen_range(0..10); let fold_count = rng.gen_range(0..10);
@ -616,93 +607,83 @@ mod tests {
fold_ranges.push(start..end); fold_ranges.push(start..end);
} }
map.fold(fold_ranges, app)?; map.fold(fold_ranges, app.as_ref()).unwrap();
let mut expected_text = buffer.text(); 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, ""); 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 = let display_point =
map.to_display_point(fold_range.start.to_point(buffer).unwrap()); map.to_display_point(fold_range.start.to_point(buffer).unwrap());
assert!(map.is_line_folded(display_point.row())); assert!(map.is_line_folded(display_point.row()));
} }
}
Ok::<(), anyhow::Error>(()) let edits = buffer.update(app, |buffer, ctx| {
})?;
let edits = buffer.update(&mut app, |buffer, ctx| {
let start_version = buffer.version.clone(); let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..10); let edit_count = rng.gen_range(1..10);
buffer.randomly_edit(&mut rng, edit_count, Some(ctx)); buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>()) buffer.edits_since(start_version).collect::<Vec<_>>()
})?; });
app.read(|app| { map.apply_edits(&edits, app.as_ref()).unwrap();
map.apply_edits(&edits, app)?;
let buffer = map.buffer.as_ref(app); let buffer = map.buffer.as_ref(app);
let mut expected_text = buffer.text(); let mut expected_text = buffer.text();
let mut expected_buffer_rows = Vec::new(); let mut expected_buffer_rows = Vec::new();
let mut next_row = buffer.max_point().row; let mut next_row = buffer.max_point().row;
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() {
let fold_start = buffer.point_for_offset(fold_range.start).unwrap(); let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
let fold_end = buffer.point_for_offset(fold_range.end).unwrap(); let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev()); expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
next_row = fold_start.row; next_row = fold_start.row;
expected_text.replace_range(fold_range.start..fold_range.end, ""); expected_text.replace_range(fold_range.start..fold_range.end, "");
} }
expected_buffer_rows.extend((0..=next_row).rev()); expected_buffer_rows.extend((0..=next_row).rev());
expected_buffer_rows.reverse(); expected_buffer_rows.reverse();
assert_eq!(map.text(app), expected_text); assert_eq!(map.text(app.as_ref()), expected_text);
for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() { for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row(); let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
assert_eq!( assert_eq!(
map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(), map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
expected_buffer_rows[idx..], expected_buffer_rows[idx..],
); );
} }
});
Ok::<(), anyhow::Error>(())
})?;
Ok::<(), anyhow::Error>(())
})?;
} }
Ok(())
} }
#[test] #[test]
fn test_buffer_rows() -> Result<()> { fn test_buffer_rows() {
App::test((), |mut app| async move { App::test((), |app| {
let text = sample_text(6, 6) + "\n"; let text = sample_text(6, 6) + "\n";
let buffer = app.add_model(|_| Buffer::new(0, text)); let buffer = app.add_model(|_| Buffer::new(0, text));
app.read(|app| { let mut map = FoldMap::new(buffer.clone(), app.as_ref());
let mut map = FoldMap::new(buffer.clone(), app);
map.fold( map.fold(
vec![ vec![
Point::new(0, 2)..Point::new(2, 2), Point::new(0, 2)..Point::new(2, 2),
Point::new(3, 1)..Point::new(4, 1), Point::new(3, 1)..Point::new(4, 1),
], ],
app, app.as_ref(),
)?; )
.unwrap();
assert_eq!(map.text(app), "aa…cccc\nd…eeeee\nffffff\n"); assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
assert_eq!(map.buffer_rows(0)?.collect::<Vec<_>>(), vec![0, 3, 5, 6]); assert_eq!(
assert_eq!(map.buffer_rows(3)?.collect::<Vec<_>>(), vec![6]); map.buffer_rows(0).unwrap().collect::<Vec<_>>(),
vec![0, 3, 5, 6]
Ok(()) );
}) assert_eq!(map.buffer_rows(3).unwrap().collect::<Vec<_>>(), vec![6]);
}) });
} }
impl FoldMap { impl FoldMap {

View file

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

View file

@ -11,7 +11,7 @@ use gpui::{
fonts::{Properties, Weight}, fonts::{Properties, Weight},
geometry::vector::vec2f, geometry::vector::vec2f,
keymap::{self, Binding}, keymap::{self, Binding},
App, AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext, AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
ViewHandle, WeakViewHandle, ViewHandle, WeakViewHandle,
}; };
use std::cmp; use std::cmp;
@ -394,20 +394,23 @@ mod tests {
editor, settings, editor, settings,
workspace::{Workspace, WorkspaceView}, workspace::{Workspace, WorkspaceView},
}; };
use anyhow::Result;
use gpui::App; use gpui::App;
use smol::fs; use smol::fs;
use tempdir::TempDir; use tempdir::TempDir;
#[test] #[test]
fn test_matching_paths() -> Result<()> { fn test_matching_paths() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
let tmp_dir = TempDir::new("example")?; let tmp_dir = TempDir::new("example").unwrap();
fs::create_dir(tmp_dir.path().join("a")).await?; fs::create_dir(tmp_dir.path().join("a")).await.unwrap();
fs::write(tmp_dir.path().join("a/banana"), "banana").await?; fs::write(tmp_dir.path().join("a/banana"), "banana")
fs::write(tmp_dir.path().join("a/bandana"), "bandana").await?; .await
super::init(&mut app); .unwrap();
editor::init(&mut app); fs::write(tmp_dir.path().join("a/bandana"), "bandana")
.await
.unwrap();
super::init(app);
editor::init(app);
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx));
@ -420,16 +423,15 @@ mod tests {
"file_finder:toggle".into(), "file_finder:toggle".into(),
(), (),
); );
let (finder, query_buffer) = workspace_view.read(&app, |view, ctx| {
let finder = view let finder = workspace_view
.modal() .as_ref(app)
.cloned() .modal()
.unwrap() .cloned()
.downcast::<FileFinder>() .unwrap()
.unwrap(); .downcast::<FileFinder>()
let query_buffer = finder.as_ref(ctx).query_buffer.clone(); .unwrap();
(finder, query_buffer) let query_buffer = finder.as_ref(app).query_buffer.clone();
});
let chain = vec![finder.id(), query_buffer.id()]; let chain = vec![finder.id(), query_buffer.id()];
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string()); app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
@ -452,7 +454,7 @@ mod tests {
// (), // (),
// ); // );
// app.finish_pending_tasks().await; // Load Buffer and open BufferView. // 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!( // assert_eq!(
// active_pane.state(&app), // active_pane.state(&app),
// pane::State { // pane::State {
@ -462,7 +464,6 @@ mod tests {
// }] // }]
// } // }
// ); // );
Ok(()) });
})
} }
} }

View file

@ -1,5 +1,5 @@
use fs::OpenOptions; use fs::OpenOptions;
use gpui::platform::{current as platform, PathPromptOptions, Runner as _}; use gpui::platform::PathPromptOptions;
use log::LevelFilter; use log::LevelFilter;
use simplelog::SimpleLogger; use simplelog::SimpleLogger;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
@ -13,6 +13,7 @@ fn main() {
let app = gpui::App::new(assets::Assets).unwrap(); let app = gpui::App::new(assets::Assets).unwrap();
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
app.set_menus(menus::MENUS);
app.on_menu_command({ app.on_menu_command({
let settings_rx = settings_rx.clone(); let settings_rx = settings_rx.clone();
move |command, ctx| match command { move |command, ctx| match command {
@ -34,7 +35,7 @@ fn main() {
_ => ctx.dispatch_global_action(command, ()), _ => ctx.dispatch_global_action(command, ()),
} }
}) })
.run(move |ctx| { .on_finish_launching(move |ctx| {
workspace::init(ctx); workspace::init(ctx);
editor::init(ctx); editor::init(ctx);
file_finder::init(ctx); file_finder::init(ctx);
@ -53,7 +54,8 @@ fn main() {
}, },
); );
} }
}); })
.run();
} }
fn init_logger() { fn init_logger() {

View file

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

View file

@ -5,7 +5,7 @@ use gpui::{
elements::*, elements::*,
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
keymap::Binding, keymap::Binding,
App, AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext, AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
}; };
use std::cmp; use std::cmp;

View file

@ -200,23 +200,22 @@ impl Entity for Workspace {
#[cfg(test)] #[cfg(test)]
pub trait WorkspaceHandle { pub trait WorkspaceHandle {
fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)>; fn file_entries(&self, app: &mut MutableAppContext) -> Vec<(usize, usize)>;
} }
#[cfg(test)] #[cfg(test)]
impl WorkspaceHandle for ModelHandle<Workspace> { impl WorkspaceHandle for ModelHandle<Workspace> {
fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)> { fn file_entries(&self, app: &mut MutableAppContext) -> Vec<(usize, usize)> {
self.read(&app, |w, app| { self.as_ref(app)
w.worktrees() .worktrees()
.iter() .iter()
.flat_map(|tree| { .flat_map(|tree| {
let tree_id = tree.id(); let tree_id = tree.id();
tree.as_ref(app) tree.as_ref(app)
.files() .files()
.map(move |file| (tree_id, file.entry_id)) .map(move |file| (tree_id, file.entry_id))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
})
} }
} }
@ -228,8 +227,8 @@ mod tests {
use serde_json::json; use serde_json::json;
#[test] #[test]
fn test_open_entry() -> Result<(), Arc<anyhow::Error>> { fn test_open_entry() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
"aa": "aa contents", "aa": "aa contents",
@ -241,32 +240,29 @@ mod tests {
app.finish_pending_tasks().await; // Open and populate worktree. app.finish_pending_tasks().await; // Open and populate worktree.
// Get the first file entry. // Get the first file entry.
let entry = workspace.read(&app, |w, app| { let tree = workspace.as_ref(app).worktrees.iter().next().unwrap();
let tree = w.worktrees.iter().next().unwrap(); let entry_id = tree.as_ref(app).files().next().unwrap().entry_id;
let entry_id = tree.as_ref(app).files().next().unwrap().entry_id; let entry = (tree.id(), entry_id);
(tree.id(), entry_id)
});
// Open the same entry twice before it finishes loading. // Open the same entry twice before it finishes loading.
let (future_1, future_2) = workspace.update(&mut app, |w, app| { let (future_1, future_2) = workspace.update(app, |w, app| {
( (
w.open_entry(entry, app).unwrap(), w.open_entry(entry, app).unwrap(),
w.open_entry(entry, app).unwrap(), w.open_entry(entry, app).unwrap(),
) )
}); });
let handle_1 = future_1.await?; let handle_1 = future_1.await.unwrap();
let handle_2 = future_2.await?; let handle_2 = future_2.await.unwrap();
assert_eq!(handle_1.id(), handle_2.id()); assert_eq!(handle_1.id(), handle_2.id());
// Open the same entry again now that it has loaded // Open the same entry again now that it has loaded
let handle_3 = workspace let handle_3 = workspace
.update(&mut app, |w, app| w.open_entry(entry, app).unwrap()) .update(app, |w, app| w.open_entry(entry, app).unwrap())
.await?; .await
.unwrap();
assert_eq!(handle_3.id(), handle_1.id()); assert_eq!(handle_3.id(), handle_1.id());
Ok(())
}) })
} }
} }

View file

@ -2,8 +2,8 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
use crate::{settings::Settings, watch}; use crate::{settings::Settings, watch};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use gpui::{ use gpui::{
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App, color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
}; };
use log::{error, info}; use log::{error, info};
use std::{collections::HashSet, path::PathBuf}; use std::{collections::HashSet, path::PathBuf};
@ -389,13 +389,12 @@ impl View for WorkspaceView {
mod tests { mod tests {
use super::{pane, Workspace, WorkspaceView}; use super::{pane, Workspace, WorkspaceView};
use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _}; use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _};
use anyhow::Result;
use gpui::App; use gpui::App;
use serde_json::json; use serde_json::json;
#[test] #[test]
fn test_open_entry() -> Result<()> { fn test_open_entry() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
"aa": "aa contents", "aa": "aa contents",
@ -407,64 +406,70 @@ mod tests {
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
app.finish_pending_tasks().await; // Open and populate worktree. app.finish_pending_tasks().await; // Open and populate worktree.
let entries = workspace.file_entries(&app); let entries = workspace.file_entries(app);
let (_, workspace_view) = let (_, workspace_view) =
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
// Open the first entry // Open the first entry
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
workspace_view.read(&app, |w, app| { assert_eq!(
assert_eq!(w.active_pane().as_ref(app).items().len(), 1); workspace_view
}); .as_ref(app)
.active_pane()
.as_ref(app)
.items()
.len(),
1
);
// Open the second entry // Open the second entry
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx)); workspace_view.update(app, |w, ctx| w.open_entry(entries[1], ctx));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
workspace_view.read(&app, |w, app| { let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app);
let active_pane = w.active_pane().as_ref(app); assert_eq!(active_pane.items().len(), 2);
assert_eq!(active_pane.items().len(), 2); assert_eq!(
assert_eq!( active_pane.active_item().unwrap().entry_id(app.as_ref()),
active_pane.active_item().unwrap().entry_id(app), Some(entries[1])
Some(entries[1]) );
);
});
// Open the first entry again // Open the first entry again
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
workspace_view.read(&app, |w, app| { let active_pane = workspace_view.as_ref(app).active_pane().as_ref(app);
let active_pane = w.active_pane().as_ref(app); assert_eq!(active_pane.items().len(), 2);
assert_eq!(active_pane.items().len(), 2); assert_eq!(
assert_eq!( active_pane.active_item().unwrap().entry_id(app.as_ref()),
active_pane.active_item().unwrap().entry_id(app), Some(entries[0])
Some(entries[0]) );
);
});
// Open the third entry twice concurrently // Open the third entry twice concurrently
workspace_view.update(&mut app, |w, ctx| { workspace_view.update(app, |w, ctx| {
w.open_entry(entries[2], ctx); w.open_entry(entries[2], ctx);
w.open_entry(entries[2], ctx); w.open_entry(entries[2], ctx);
}); });
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
workspace_view.read(&app, |w, app| { assert_eq!(
assert_eq!(w.active_pane().as_ref(app).items().len(), 3); workspace_view
}); .as_ref(app)
.active_pane()
Ok(()) .as_ref(app)
}) .items()
.len(),
3
);
});
} }
#[test] #[test]
fn test_pane_actions() -> Result<()> { fn test_pane_actions() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
pane::init(&mut app); pane::init(app);
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
@ -474,35 +479,37 @@ mod tests {
}, },
})); }));
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(app.font_cache()).unwrap().1;
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
app.finish_pending_tasks().await; // Open and populate worktree. app.finish_pending_tasks().await; // Open and populate worktree.
let entries = workspace.file_entries(&app); let entries = workspace.file_entries(app);
let (window_id, workspace_view) = let (window_id, workspace_view) =
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx)); workspace_view.update(app, |w, ctx| w.open_entry(entries[0], ctx));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
let pane_1 = workspace_view.read(&app, |w, _| w.active_pane().clone()); let pane_1 = workspace_view.as_ref(app).active_pane().clone();
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ()); app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
let pane_2 = workspace_view.read(&app, |w, _| w.active_pane().clone()); let pane_2 = workspace_view.as_ref(app).active_pane().clone();
assert_ne!(pane_1, pane_2); assert_ne!(pane_1, pane_2);
pane_2.read(&app, |p, app| { assert_eq!(
assert_eq!(p.active_item().unwrap().entry_id(app), Some(entries[0])); pane_2
}); .as_ref(app)
.active_item()
.unwrap()
.entry_id(app.downgrade()),
Some(entries[0])
);
app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ()); app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
workspace_view.read(&app, |w, _| { let w = workspace_view.as_ref(app);
assert_eq!(w.panes.len(), 1); assert_eq!(w.panes.len(), 1);
assert_eq!(w.active_pane(), &pane_1) assert_eq!(w.active_pane(), &pane_1);
}); });
Ok(())
})
} }
} }

View file

@ -648,8 +648,8 @@ mod test {
use std::os::unix; use std::os::unix;
#[test] #[test]
fn test_populate_and_search() -> Result<()> { fn test_populate_and_search() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"root": { "root": {
"apple": "", "apple": "",
@ -666,33 +666,31 @@ mod test {
})); }));
let root_link_path = dir.path().join("root_link"); 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))); let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx)));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
tree.read(&app, |tree, _| { let tree = tree.as_ref(app);
assert_eq!(tree.file_count(), 4); assert_eq!(tree.file_count(), 4);
let results = match_paths(&[tree.clone()], "bna", false, false, 10) let results = match_paths(&[tree.clone()], "bna", false, false, 10)
.iter() .iter()
.map(|result| tree.entry_path(result.entry_id)) .map(|result| tree.entry_path(result.entry_id))
.collect::<Result<Vec<PathBuf>, _>>() .collect::<Result<Vec<PathBuf>, _>>()
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
results, results,
vec![ vec![
PathBuf::from("root_link/banana/carrot/date"), PathBuf::from("root_link/banana/carrot/date"),
PathBuf::from("root_link/banana/carrot/endive"), PathBuf::from("root_link/banana/carrot/endive"),
] ]
); );
}); });
Ok(())
})
} }
#[test] #[test]
fn test_save_file() { fn test_save_file() {
App::test((), |mut app| async move { App::test_async((), |app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"file1": "the old contents", "file1": "the old contents",
})); }));
@ -700,24 +698,18 @@ mod test {
let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx))); let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx)));
app.finish_pending_tasks().await; app.finish_pending_tasks().await;
let file_id = tree.read(&app, |tree, _| { let entry = tree.as_ref(app).files().next().unwrap();
let entry = tree.files().next().unwrap(); assert_eq!(entry.path.file_name().unwrap(), "file1");
assert_eq!(entry.path.file_name().unwrap(), "file1"); let file_id = entry.entry_id;
entry.entry_id
});
let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024)); let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
tree.update(&mut app, |tree, ctx| { tree.update(app, |tree, ctx| {
smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap() smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap()
}); });
let history = tree let history = tree.as_ref(app).load_history(file_id).await.unwrap();
.read(&app, |tree, _| tree.load_history(file_id))
.await
.unwrap();
assert_eq!(history.base_text, buffer.text()); assert_eq!(history.base_text, buffer.text());
}) });
} }
} }