Introduce AsyncAppContext and simplify spawning
Now when you call spawn in various context, you pass an FnOnce that is called with an AsyncAppContext and returns a static future. This allows you to write async code similar to how our tests work, borrowing the guts of the AsyncAppContext when needed to interact, but using normal async await semantics instead of callbacks. Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
co-authored by
Max Brunsfeld
parent
d0f69c76e1
commit
fa6bd1f926
@@ -4,11 +4,9 @@ mod selection;
|
||||
mod text;
|
||||
|
||||
pub use anchor::*;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
pub use point::*;
|
||||
use seahash::SeaHasher;
|
||||
pub use selection::*;
|
||||
use smol::future::FutureExt;
|
||||
pub use text::*;
|
||||
|
||||
use crate::{
|
||||
@@ -19,7 +17,7 @@ use crate::{
|
||||
worktree::FileHandle,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui::{Entity, ModelContext};
|
||||
use gpui::{Entity, ModelContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
@@ -475,21 +473,23 @@ impl Buffer {
|
||||
&mut self,
|
||||
new_file: Option<FileHandle>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) -> LocalBoxFuture<'static, Result<()>> {
|
||||
) -> Task<Result<()>> {
|
||||
let snapshot = self.snapshot();
|
||||
let version = self.version.clone();
|
||||
if let Some(file) = new_file.as_ref().or(self.file.as_ref()) {
|
||||
let save_task = file.save(snapshot, ctx.as_ref());
|
||||
ctx.spawn(save_task, |me, save_result, ctx| {
|
||||
if save_result.is_ok() {
|
||||
me.did_save(version, new_file, ctx);
|
||||
let file = self.file.clone();
|
||||
let handle = ctx.handle();
|
||||
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
if let Some(file) = new_file.as_ref().or(file.as_ref()) {
|
||||
let result = ctx.read(|ctx| file.save(snapshot, ctx.as_ref())).await;
|
||||
if result.is_ok() {
|
||||
handle.update(&mut ctx, |me, ctx| me.did_save(version, new_file, ctx));
|
||||
}
|
||||
save_result
|
||||
})
|
||||
.boxed_local()
|
||||
} else {
|
||||
async { Ok(()) }.boxed_local()
|
||||
}
|
||||
result
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn did_save(
|
||||
|
||||
@@ -4,11 +4,10 @@ use super::{
|
||||
};
|
||||
use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle};
|
||||
use anyhow::Result;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use gpui::{
|
||||
fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
|
||||
MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle,
|
||||
MutableAppContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use postage::watch;
|
||||
@@ -2348,13 +2347,13 @@ impl BufferView {
|
||||
ctx.notify();
|
||||
|
||||
let epoch = self.next_blink_epoch();
|
||||
ctx.spawn(
|
||||
async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
epoch
|
||||
},
|
||||
Self::resume_cursor_blinking,
|
||||
)
|
||||
let handle = ctx.handle();
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
handle.update(&mut ctx, |this, ctx| {
|
||||
this.resume_cursor_blinking(epoch, ctx);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -2371,13 +2370,11 @@ impl BufferView {
|
||||
ctx.notify();
|
||||
|
||||
let epoch = self.next_blink_epoch();
|
||||
ctx.spawn(
|
||||
async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
epoch
|
||||
},
|
||||
Self::blink_cursors,
|
||||
)
|
||||
let handle = ctx.handle();
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
handle.update(&mut ctx, |this, ctx| this.blink_cursors(epoch, ctx));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
@@ -2498,7 +2495,7 @@ impl workspace::ItemView for BufferView {
|
||||
&mut self,
|
||||
new_file: Option<FileHandle>,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> LocalBoxFuture<'static, Result<()>> {
|
||||
) -> Task<Result<()>> {
|
||||
self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ impl FileFinder {
|
||||
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
|
||||
self.cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
let cancel_flag = self.cancel_flag.clone();
|
||||
let task = ctx.background_executor().spawn(async move {
|
||||
let background_task = ctx.background_executor().spawn(async move {
|
||||
let include_root_name = snapshots.len() > 1;
|
||||
let matches = match_paths(
|
||||
snapshots.iter(),
|
||||
@@ -415,7 +415,12 @@ impl FileFinder {
|
||||
(search_id, did_cancel, query, matches)
|
||||
});
|
||||
|
||||
ctx.spawn(task, Self::update_matches).detach();
|
||||
let handle = ctx.handle();
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
let matches = background_task.await;
|
||||
handle.update(&mut ctx, |this, ctx| this.update_matches(matches, ctx));
|
||||
})
|
||||
.detach();
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
+50
-46
@@ -6,10 +6,10 @@ use crate::{
|
||||
time::ReplicaId,
|
||||
worktree::{FileHandle, Worktree, WorktreeHandle},
|
||||
};
|
||||
use futures_core::{future::LocalBoxFuture, Future};
|
||||
use futures_core::Future;
|
||||
use gpui::{
|
||||
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
|
||||
ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions, View,
|
||||
ClipboardItem, Entity, ModelHandle, MutableAppContext, PathPromptOptions, Task, View,
|
||||
ViewContext, ViewHandle, WeakModelHandle,
|
||||
};
|
||||
use log::error;
|
||||
@@ -123,7 +123,7 @@ pub trait ItemView: View {
|
||||
&mut self,
|
||||
_: Option<FileHandle>,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> LocalBoxFuture<'static, anyhow::Result<()>>;
|
||||
) -> Task<anyhow::Result<()>>;
|
||||
fn should_activate_item_on_event(_: &Self::Event) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -161,7 +161,7 @@ pub trait ItemViewHandle: Send + Sync {
|
||||
&self,
|
||||
file: Option<FileHandle>,
|
||||
ctx: &mut MutableAppContext,
|
||||
) -> LocalBoxFuture<'static, anyhow::Result<()>>;
|
||||
) -> Task<anyhow::Result<()>>;
|
||||
}
|
||||
|
||||
impl<T: Item> ItemHandle for ModelHandle<T> {
|
||||
@@ -239,7 +239,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
|
||||
&self,
|
||||
file: Option<FileHandle>,
|
||||
ctx: &mut MutableAppContext,
|
||||
) -> LocalBoxFuture<'static, anyhow::Result<()>> {
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
self.update(ctx, |item, ctx| item.save(file, ctx))
|
||||
}
|
||||
|
||||
@@ -359,16 +359,18 @@ impl Workspace {
|
||||
.cloned()
|
||||
.zip(entries.into_iter())
|
||||
.map(|(abs_path, file)| {
|
||||
ctx.spawn(
|
||||
bg.spawn(async move { abs_path.is_file() }),
|
||||
move |me, is_file, ctx| {
|
||||
let handle = ctx.handle();
|
||||
let is_file = bg.spawn(async move { abs_path.is_file() });
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
let is_file = is_file.await;
|
||||
handle.update(&mut ctx, |me, ctx| {
|
||||
if is_file {
|
||||
me.open_entry(file.entry_id(), ctx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
async move {
|
||||
@@ -442,7 +444,7 @@ impl Workspace {
|
||||
&mut self,
|
||||
entry: (usize, Arc<Path>),
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> Option<EntityTask<()>> {
|
||||
) -> Option<Task<()>> {
|
||||
// If the active pane contains a view for this file, then activate
|
||||
// that item view.
|
||||
if self
|
||||
@@ -496,28 +498,31 @@ impl Workspace {
|
||||
let history = ctx
|
||||
.background_executor()
|
||||
.spawn(file.load_history(ctx.as_ref()));
|
||||
ctx.spawn(history, move |_, history, ctx| {
|
||||
*tx.borrow_mut() = Some(match history {
|
||||
Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
|
||||
Buffer::from_history(replica_id, history, Some(file), ctx)
|
||||
}))),
|
||||
Err(error) => Err(Arc::new(error)),
|
||||
|
||||
ctx.as_mut()
|
||||
.spawn(|mut ctx| async move {
|
||||
*tx.borrow_mut() = Some(match history.await {
|
||||
Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
|
||||
Buffer::from_history(replica_id, history, Some(file), ctx)
|
||||
}))),
|
||||
Err(error) => Err(Arc::new(error)),
|
||||
})
|
||||
})
|
||||
})
|
||||
.detach()
|
||||
.detach();
|
||||
}
|
||||
|
||||
let mut watch = self.loading_items.get(&entry).unwrap().clone();
|
||||
Some(ctx.spawn(
|
||||
async move {
|
||||
loop {
|
||||
if let Some(load_result) = watch.borrow().as_ref() {
|
||||
return load_result.clone();
|
||||
}
|
||||
watch.next().await;
|
||||
|
||||
let handle = ctx.handle();
|
||||
Some(ctx.spawn(|mut ctx| async move {
|
||||
let load_result = loop {
|
||||
if let Some(load_result) = watch.borrow().as_ref() {
|
||||
break load_result.clone();
|
||||
}
|
||||
},
|
||||
move |me, load_result, ctx| {
|
||||
watch.next().await;
|
||||
};
|
||||
|
||||
handle.update(&mut ctx, |me, ctx| {
|
||||
me.loading_items.remove(&entry);
|
||||
match load_result {
|
||||
Ok(item) => {
|
||||
@@ -532,8 +537,8 @@ impl Workspace {
|
||||
log::error!("error opening item: {}", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
))
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn active_item(&self, ctx: &ViewContext<Self>) -> Option<Box<dyn ItemViewHandle>> {
|
||||
@@ -552,28 +557,27 @@ impl Workspace {
|
||||
.to_path_buf();
|
||||
ctx.prompt_for_new_path(&start_path, move |path, ctx| {
|
||||
if let Some(path) = path {
|
||||
handle.update(ctx, move |this, ctx| {
|
||||
let file = this.file_for_path(&path, ctx);
|
||||
let task = item.save(Some(file), ctx.as_mut());
|
||||
ctx.spawn(task, move |_, result, _| {
|
||||
if let Err(e) = result {
|
||||
error!("failed to save item: {:?}, ", e);
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
let file =
|
||||
handle.update(&mut ctx, |me, ctx| me.file_for_path(&path, ctx));
|
||||
if let Err(error) = ctx.update(|ctx| item.save(Some(file), ctx)).await {
|
||||
error!("failed to save item: {:?}, ", error);
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let task = item.save(None, ctx.as_mut());
|
||||
ctx.spawn(task, |_, result, _| {
|
||||
if let Err(e) = result {
|
||||
error!("failed to save item: {:?}, ", e);
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
let save = item.save(None, ctx.as_mut());
|
||||
ctx.foreground()
|
||||
.spawn(async move {
|
||||
if let Err(e) = save.await {
|
||||
error!("failed to save item: {:?}, ", e);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-14
@@ -16,7 +16,7 @@ use postage::{
|
||||
prelude::{Sink, Stream},
|
||||
watch,
|
||||
};
|
||||
use smol::{channel::Sender, Timer};
|
||||
use smol::channel::Sender;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::{HashMap, HashSet},
|
||||
@@ -98,8 +98,24 @@ impl Worktree {
|
||||
scanner.run(event_stream)
|
||||
});
|
||||
|
||||
ctx.spawn_stream(scan_state_rx, Self::observe_scan_state, |_, _| {})
|
||||
.detach();
|
||||
let handle = ctx.handle().downgrade();
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
while let Ok(scan_state) = scan_state_rx.recv().await {
|
||||
let alive = ctx.update(|ctx| {
|
||||
if let Some(handle) = handle.upgrade(&ctx) {
|
||||
handle.update(ctx, |this, ctx| this.observe_scan_state(scan_state, ctx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !alive {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
tree
|
||||
}
|
||||
@@ -116,15 +132,17 @@ impl Worktree {
|
||||
|
||||
pub fn next_scan_complete(&self, ctx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
|
||||
let scan_id = self.snapshot.scan_id;
|
||||
ctx.spawn_stream(
|
||||
self.scan_state.1.clone(),
|
||||
move |this, scan_state, ctx| {
|
||||
if matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id {
|
||||
ctx.halt_stream();
|
||||
let mut scan_state = self.scan_state.1.clone();
|
||||
let handle = ctx.handle();
|
||||
ctx.spawn(|ctx| async move {
|
||||
while let Some(scan_state) = scan_state.recv().await {
|
||||
if handle.read_with(&ctx, |this, _| {
|
||||
matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id
|
||||
}) {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|_, _| {},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
|
||||
@@ -137,9 +155,12 @@ impl Worktree {
|
||||
ctx.notify();
|
||||
|
||||
if self.is_scanning() && !self.poll_scheduled {
|
||||
ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
|
||||
this.poll_scheduled = false;
|
||||
this.poll_entries(ctx);
|
||||
let handle = ctx.handle();
|
||||
ctx.spawn(|mut ctx| async move {
|
||||
handle.update(&mut ctx, |this, ctx| {
|
||||
this.poll_scheduled = false;
|
||||
this.poll_entries(ctx);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
self.poll_scheduled = true;
|
||||
|
||||
Reference in New Issue
Block a user