Compare commits
4 Commits
main
...
push-xmyns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f289ee7223 | ||
|
|
f2d1fd091e | ||
|
|
70855a0267 | ||
|
|
d0ec591b23 |
@@ -2235,7 +2235,9 @@ impl Editor {
|
||||
diagnostics_max_severity,
|
||||
hard_wrap: None,
|
||||
completion_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||
semantics_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||
semantics_provider: project
|
||||
.as_ref()
|
||||
.map(|project| Rc::new(project.downgrade()) as _),
|
||||
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
||||
project,
|
||||
blink_manager: blink_manager.clone(),
|
||||
@@ -21923,7 +21925,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(project) = self.project.clone() else {
|
||||
let Some(semantics) = self.semantics_provider.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -21958,7 +21960,7 @@ impl Editor {
|
||||
let range =
|
||||
buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
|
||||
|
||||
project.inline_values(buffer, range, cx)
|
||||
semantics.inline_values(buffer, range, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()?
|
||||
@@ -24499,14 +24501,15 @@ impl CompletionProvider for Entity<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticsProvider for Entity<Project> {
|
||||
impl SemanticsProvider for WeakEntity<Project> {
|
||||
fn hover(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
|
||||
self.update(cx, |project, cx| project.hover(buffer, position, cx))
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn document_highlights(
|
||||
@@ -24515,9 +24518,10 @@ impl SemanticsProvider for Entity<Project> {
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
|
||||
Some(self.update(cx, |project, cx| {
|
||||
self.update(cx, |project, cx| {
|
||||
project.document_highlights(buffer, position, cx)
|
||||
}))
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn definitions(
|
||||
@@ -24527,12 +24531,13 @@ impl SemanticsProvider for Entity<Project> {
|
||||
kind: GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
|
||||
Some(self.update(cx, |project, cx| match kind {
|
||||
self.update(cx, |project, cx| match kind {
|
||||
GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
|
||||
GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
|
||||
GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
|
||||
GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
|
||||
}))
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
|
||||
@@ -24548,6 +24553,7 @@ impl SemanticsProvider for Entity<Project> {
|
||||
project.any_language_server_supports_inlay_hints(buffer, cx)
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn inline_values(
|
||||
@@ -24561,6 +24567,8 @@ impl SemanticsProvider for Entity<Project> {
|
||||
|
||||
Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn applicable_inlay_chunks(
|
||||
@@ -24569,15 +24577,21 @@ impl SemanticsProvider for Entity<Project> {
|
||||
ranges: &[Range<text::Anchor>],
|
||||
cx: &mut App,
|
||||
) -> Vec<Range<BufferRow>> {
|
||||
self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
|
||||
self.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
|
||||
self.read(cx).lsp_store().update(cx, |lsp_store, _| {
|
||||
lsp_store.invalidate_inlay_hints(for_buffers)
|
||||
});
|
||||
self.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, _| {
|
||||
lsp_store.invalidate_inlay_hints(for_buffers)
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn inlay_hints(
|
||||
@@ -24588,9 +24602,12 @@ impl SemanticsProvider for Entity<Project> {
|
||||
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
|
||||
cx: &mut App,
|
||||
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
|
||||
Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
|
||||
}))
|
||||
self.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn range_for_rename(
|
||||
@@ -24599,7 +24616,7 @@ impl SemanticsProvider for Entity<Project> {
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
|
||||
Some(self.update(cx, |project, cx| {
|
||||
self.update(cx, |project, cx| {
|
||||
let buffer = buffer.clone();
|
||||
let task = project.prepare_rename(buffer.clone(), position, cx);
|
||||
cx.spawn(async move |_, cx| {
|
||||
@@ -24622,7 +24639,8 @@ impl SemanticsProvider for Entity<Project> {
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn perform_rename(
|
||||
@@ -24632,9 +24650,10 @@ impl SemanticsProvider for Entity<Project> {
|
||||
new_name: String,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<ProjectTransaction>>> {
|
||||
Some(self.update(cx, |project, cx| {
|
||||
self.update(cx, |project, cx| {
|
||||
project.perform_rename(buffer.clone(), position, new_name, cx)
|
||||
}))
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,8 +123,6 @@ pub fn assert_text_with_selections(
|
||||
assert_eq!(actual, marked_text, "Selections don't match");
|
||||
}
|
||||
|
||||
// RA thinks this is dead code even though it is used in a whole lot of tests
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn build_editor(
|
||||
buffer: Entity<MultiBuffer>,
|
||||
|
||||
@@ -743,6 +743,11 @@ impl App {
|
||||
app
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn ref_counts_drop_handle(&self) -> impl Sized + use<> {
|
||||
self.entities.ref_counts_drop_handle()
|
||||
}
|
||||
|
||||
/// Quit the application gracefully. Handlers registered with [`Context::on_app_quit`]
|
||||
/// will be given 100ms to complete before exiting.
|
||||
pub fn shutdown(&mut self) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
Entity, EventEmitter, Focusable, ForegroundExecutor, Global, PromptButton, PromptLevel, Render,
|
||||
Reservation, Result, Subscription, Task, VisualContext, Window, WindowHandle,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use anyhow::{Context as _, anyhow, bail};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::channel::oneshot;
|
||||
use std::{future::Future, rc::Weak};
|
||||
@@ -29,12 +29,18 @@ impl AppContext for AsyncApp {
|
||||
) -> Self::Result<Entity<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.new(build_entity))
|
||||
}
|
||||
|
||||
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.reserve_entity())
|
||||
}
|
||||
|
||||
@@ -45,6 +51,9 @@ impl AppContext for AsyncApp {
|
||||
) -> Result<Entity<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.insert_entity(reservation, build_entity))
|
||||
}
|
||||
|
||||
@@ -55,6 +64,9 @@ impl AppContext for AsyncApp {
|
||||
) -> Self::Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.update_entity(handle, update))
|
||||
}
|
||||
|
||||
@@ -77,6 +89,9 @@ impl AppContext for AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(lock.read_entity(handle, callback))
|
||||
}
|
||||
|
||||
@@ -86,6 +101,9 @@ impl AppContext for AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.try_borrow_mut()?;
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
|
||||
@@ -99,6 +117,9 @@ impl AppContext for AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
lock.read_window(window, read)
|
||||
}
|
||||
|
||||
@@ -115,6 +136,9 @@ impl AppContext for AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(lock.update(|this| this.read_global(callback)))
|
||||
}
|
||||
}
|
||||
@@ -124,6 +148,9 @@ impl AsyncApp {
|
||||
pub fn refresh(&self) -> Result<()> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
lock.refresh_windows();
|
||||
Ok(())
|
||||
}
|
||||
@@ -142,6 +169,9 @@ impl AsyncApp {
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(lock.update(f))
|
||||
}
|
||||
|
||||
@@ -158,6 +188,9 @@ impl AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
let subscription = lock.subscribe(entity, on_event);
|
||||
Ok(subscription)
|
||||
}
|
||||
@@ -173,6 +206,9 @@ impl AsyncApp {
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
if lock.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
lock.open_window(options, build_root_view)
|
||||
}
|
||||
|
||||
@@ -193,6 +229,9 @@ impl AsyncApp {
|
||||
pub fn has_global<G: Global>(&self) -> Result<bool> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.has_global::<G>())
|
||||
}
|
||||
|
||||
@@ -203,6 +242,9 @@ impl AsyncApp {
|
||||
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(read(app.global(), &app))
|
||||
}
|
||||
|
||||
@@ -215,6 +257,9 @@ impl AsyncApp {
|
||||
pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
|
||||
let app = self.app.upgrade()?;
|
||||
let app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
return None;
|
||||
}
|
||||
Some(read(app.try_global()?, &app))
|
||||
}
|
||||
|
||||
@@ -229,6 +274,9 @@ impl AsyncApp {
|
||||
) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
app.update(|cx| {
|
||||
cx.default_global::<G>();
|
||||
});
|
||||
@@ -243,6 +291,9 @@ impl AsyncApp {
|
||||
) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
if app.quitting {
|
||||
bail!("app is quitting");
|
||||
}
|
||||
Ok(app.update(|cx| cx.update_global(update)))
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,11 @@ impl EntityMap {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn ref_counts_drop_handle(&self) -> impl Sized + use<> {
|
||||
self.ref_counts.clone()
|
||||
}
|
||||
|
||||
/// Reserve a slot for an entity, which you can subsequently use with `insert`.
|
||||
pub fn reserve<T: 'static>(&self) -> Slot<T> {
|
||||
let id = self.ref_counts.write().counts.insert(1.into());
|
||||
@@ -227,7 +232,12 @@ pub struct AnyEntity {
|
||||
}
|
||||
|
||||
impl AnyEntity {
|
||||
fn new(id: EntityId, entity_type: TypeId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self {
|
||||
fn new(
|
||||
id: EntityId,
|
||||
entity_type: TypeId,
|
||||
entity_map: Weak<RwLock<EntityRefCounts>>,
|
||||
#[cfg(any(test, feature = "leak-detection"))] type_name: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
entity_id: id,
|
||||
entity_type,
|
||||
@@ -238,7 +248,7 @@ impl AnyEntity {
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(id),
|
||||
.handle_created(id, Some(type_name)),
|
||||
entity_map,
|
||||
}
|
||||
}
|
||||
@@ -301,7 +311,7 @@ impl Clone for AnyEntity {
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(self.entity_id),
|
||||
.handle_created(self.entity_id, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,7 +407,13 @@ impl<T: 'static> Entity<T> {
|
||||
T: 'static,
|
||||
{
|
||||
Self {
|
||||
any_entity: AnyEntity::new(id, TypeId::of::<T>(), entity_map),
|
||||
any_entity: AnyEntity::new(
|
||||
id,
|
||||
TypeId::of::<T>(),
|
||||
entity_map,
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
std::any::type_name::<T>(),
|
||||
),
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -580,7 +596,7 @@ impl AnyWeakEntity {
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(self.entity_id),
|
||||
.handle_created(self.entity_id, None),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -907,7 +923,13 @@ pub(crate) struct HandleId {
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
pub(crate) struct LeakDetector {
|
||||
next_handle_id: u64,
|
||||
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
|
||||
entity_handles: HashMap<EntityId, EntityLeakData>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
struct EntityLeakData {
|
||||
handles: HashMap<HandleId, Option<backtrace::Backtrace>>,
|
||||
type_name: &'static str,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
@@ -918,11 +940,21 @@ impl LeakDetector {
|
||||
/// the handle is dropped. If `LEAK_BACKTRACE` is set, captures a backtrace
|
||||
/// at the allocation site.
|
||||
#[track_caller]
|
||||
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
|
||||
pub fn handle_created(
|
||||
&mut self,
|
||||
entity_id: EntityId,
|
||||
type_name: Option<&'static str>,
|
||||
) -> HandleId {
|
||||
let id = util::post_inc(&mut self.next_handle_id);
|
||||
let handle_id = HandleId { id };
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
handles.insert(
|
||||
let handles = self
|
||||
.entity_handles
|
||||
.entry(entity_id)
|
||||
.or_insert_with(|| EntityLeakData {
|
||||
handles: HashMap::default(),
|
||||
type_name: type_name.unwrap_or("<unknown>"),
|
||||
});
|
||||
handles.handles.insert(
|
||||
handle_id,
|
||||
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
|
||||
);
|
||||
@@ -934,8 +966,14 @@ impl LeakDetector {
|
||||
/// This removes the handle from tracking. The `handle_id` should be the same
|
||||
/// one returned by `handle_created` when the handle was allocated.
|
||||
pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) {
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
handles.remove(&handle_id);
|
||||
if let std::collections::hash_map::Entry::Occupied(mut data) =
|
||||
self.entity_handles.entry(entity_id)
|
||||
{
|
||||
data.get_mut().handles.remove(&handle_id);
|
||||
if data.get().handles.is_empty() {
|
||||
data.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that all handles to the given entity have been released.
|
||||
@@ -947,11 +985,10 @@ impl LeakDetector {
|
||||
/// otherwise it suggests setting the environment variable to get more info.
|
||||
pub fn assert_released(&mut self, entity_id: EntityId) {
|
||||
use std::fmt::Write as _;
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
if !handles.is_empty() {
|
||||
if let Some(data) = self.entity_handles.remove(&entity_id) {
|
||||
let mut out = String::new();
|
||||
for backtrace in handles.values_mut() {
|
||||
if let Some(mut backtrace) = backtrace.take() {
|
||||
for (_, backtrace) in data.handles {
|
||||
if let Some(mut backtrace) = backtrace {
|
||||
backtrace.resolve();
|
||||
writeln!(out, "Leaked handle:\n{:?}", backtrace).unwrap();
|
||||
} else {
|
||||
@@ -967,6 +1004,40 @@ impl LeakDetector {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
impl Drop for LeakDetector {
|
||||
fn drop(&mut self) {
|
||||
use std::fmt::Write;
|
||||
|
||||
if self.entity_handles.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut out = String::new();
|
||||
for (entity_id, data) in self.entity_handles.drain() {
|
||||
for (_handle, backtrace) in data.handles {
|
||||
if let Some(mut backtrace) = backtrace {
|
||||
backtrace.resolve();
|
||||
writeln!(
|
||||
out,
|
||||
"Leaked handle for entity {} ({entity_id:?}):\n{:?}",
|
||||
data.type_name, backtrace
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
out,
|
||||
"Leaked handle for entity {} ({entity_id:?}): (export LEAK_BACKTRACE to find allocation site)",
|
||||
data.type_name
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Exited with leaked handles:\n{out}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::EntityMap;
|
||||
|
||||
@@ -18,18 +18,17 @@ use std::{
|
||||
/// an implementation of `Context` with additional methods that are useful in tests.
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
#[doc(hidden)]
|
||||
pub app: Rc<AppCell>,
|
||||
#[doc(hidden)]
|
||||
pub background_executor: BackgroundExecutor,
|
||||
#[doc(hidden)]
|
||||
pub foreground_executor: ForegroundExecutor,
|
||||
#[doc(hidden)]
|
||||
pub dispatcher: TestDispatcher,
|
||||
dispatcher: TestDispatcher,
|
||||
test_platform: Rc<TestPlatform>,
|
||||
text_system: Arc<TextSystem>,
|
||||
fn_name: Option<&'static str>,
|
||||
on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
|
||||
#[doc(hidden)]
|
||||
pub app: Rc<AppCell>,
|
||||
}
|
||||
|
||||
impl AppContext for TestAppContext {
|
||||
@@ -147,6 +146,11 @@ impl TestAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops all outstanding tasks from the dispatcher.
|
||||
pub fn drain_tasks(&self) {
|
||||
// self.dispatcher.drain_tasks();
|
||||
}
|
||||
|
||||
/// Skip all drawing operations for the duration of this test.
|
||||
pub fn skip_drawing(&mut self) {
|
||||
self.app.borrow_mut().mode = GpuiMode::Test { skip_drawing: true };
|
||||
@@ -411,8 +415,8 @@ impl TestAppContext {
|
||||
}
|
||||
|
||||
/// Wait until there are no more pending tasks.
|
||||
pub fn run_until_parked(&mut self) {
|
||||
self.background_executor.run_until_parked()
|
||||
pub fn run_until_parked(&self) {
|
||||
self.dispatcher.run_until_parked();
|
||||
}
|
||||
|
||||
/// Simulate dispatching an action to the currently focused node in the window.
|
||||
|
||||
@@ -675,6 +675,11 @@ impl ForegroundExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn get_current_thread_timings(&self) -> Vec<TaskTiming> {
|
||||
self.dispatcher.get_current_thread_timings()
|
||||
}
|
||||
|
||||
/// Enqueues the given Task to run on the main thread at some point in the future.
|
||||
#[track_caller]
|
||||
pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
|
||||
@@ -751,9 +756,11 @@ where
|
||||
|
||||
impl<F> Drop for Checked<F> {
|
||||
fn drop(&mut self) {
|
||||
assert!(
|
||||
self.id == thread_id(),
|
||||
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
|
||||
assert_eq!(
|
||||
self.id,
|
||||
thread_id(),
|
||||
"local task dropped by thread {} that didn't spawn it. Task spawned at {}",
|
||||
std::thread::current().name().unwrap_or("unknown"),
|
||||
self.location
|
||||
);
|
||||
unsafe { ManuallyDrop::drop(&mut self.inner) };
|
||||
@@ -764,9 +771,11 @@ where
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
assert!(
|
||||
self.id == thread_id(),
|
||||
"local task polled by a thread that didn't spawn it. Task spawned at {}",
|
||||
assert_eq!(
|
||||
self.id,
|
||||
thread_id(),
|
||||
"local task polled by thread {} that didn't spawn it. Task spawned at {}",
|
||||
std::thread::current().name().unwrap_or("unknown"),
|
||||
self.location
|
||||
);
|
||||
unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(clippy::type_complexity)] // Not useful, GPUI makes heavy use of callbacks
|
||||
#![allow(clippy::collapsible_else_if)] // False positives in platform specific code
|
||||
#![allow(unused_mut)] // False positives in platform specific code
|
||||
|
||||
@@ -14,8 +14,8 @@ use std::{
|
||||
};
|
||||
use util::post_inc;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct TestDispatcherId(usize);
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TestDispatcherId(usize);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct TestDispatcher {
|
||||
@@ -67,6 +67,32 @@ impl TestDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain_tasks(&self) {
|
||||
// dropping runnables may reschedule tasks
|
||||
// due to drop impls with executors in them
|
||||
// so drop until we reach a fixpoint
|
||||
loop {
|
||||
let mut state = self.state.lock();
|
||||
if state.background.is_empty()
|
||||
&& state.deprioritized_background.is_empty()
|
||||
&& state.delayed.is_empty()
|
||||
&& state.foreground.is_empty()
|
||||
{
|
||||
break;
|
||||
}
|
||||
let background = std::mem::take(&mut state.background);
|
||||
let deprioritized_background = std::mem::take(&mut state.deprioritized_background);
|
||||
let delayed = std::mem::take(&mut state.delayed);
|
||||
let foreground = std::mem::take(&mut state.foreground);
|
||||
drop(state);
|
||||
|
||||
drop(background);
|
||||
drop(deprioritized_background);
|
||||
drop(delayed);
|
||||
drop(foreground);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_clock(&self, by: Duration) {
|
||||
let new_now = self.state.lock().time + by;
|
||||
loop {
|
||||
|
||||
@@ -165,12 +165,13 @@ fn generate_test_function(
|
||||
dispatcher.clone(),
|
||||
Some(stringify!(#outer_fn_name)),
|
||||
);
|
||||
let _entity_refcounts = #cx_varname.app.borrow().ref_counts_drop_handle();
|
||||
));
|
||||
cx_teardowns.extend(quote!(
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.executor().forbid_parking();
|
||||
#cx_varname.quit();
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.run_until_parked();
|
||||
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
|
||||
#cx_varname.run_until_parked();
|
||||
drop(#cx_varname);
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
continue;
|
||||
@@ -191,10 +192,19 @@ fn generate_test_function(
|
||||
&[#seeds],
|
||||
#max_retries,
|
||||
&mut |dispatcher, _seed| {
|
||||
let executor = gpui::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
|
||||
let exec = std::sync::Arc::new(dispatcher.clone());
|
||||
#cx_vars
|
||||
executor.block_test(#inner_fn_name(#inner_fn_args));
|
||||
gpui::BackgroundExecutor::new(exec.clone()).block_test(#inner_fn_name(#inner_fn_args));
|
||||
drop(exec);
|
||||
#cx_teardowns
|
||||
// Ideally we would only drop cancelled tasks, that way we could detect leaks due to task <-> entity
|
||||
// cycles as cancelled tasks will be dropped properly once they runnable gets run again
|
||||
// We can't do that in tests though, as well running tasks will run logic
|
||||
// and we might just not exit that way
|
||||
//
|
||||
// async-task does not give us the power to do this just yet though
|
||||
dispatcher.drain_tasks();
|
||||
drop(dispatcher);
|
||||
},
|
||||
#on_failure_fn_name
|
||||
);
|
||||
@@ -229,13 +239,15 @@ fn generate_test_function(
|
||||
Some(stringify!(#outer_fn_name))
|
||||
);
|
||||
let mut #cx_varname_lock = #cx_varname.app.borrow_mut();
|
||||
let _entity_refcounts = #cx_varname_lock.ref_counts_drop_handle();
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname_lock,));
|
||||
cx_teardowns.extend(quote!(
|
||||
drop(#cx_varname_lock);
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.run_until_parked();
|
||||
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.run_until_parked();
|
||||
drop(#cx_varname);
|
||||
));
|
||||
continue;
|
||||
}
|
||||
@@ -246,12 +258,13 @@ fn generate_test_function(
|
||||
dispatcher.clone(),
|
||||
Some(stringify!(#outer_fn_name))
|
||||
);
|
||||
let _entity_refcounts = #cx_varname.app.borrow().ref_counts_drop_handle();
|
||||
));
|
||||
cx_teardowns.extend(quote!(
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.executor().forbid_parking();
|
||||
#cx_varname.quit();
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.run_until_parked();
|
||||
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
|
||||
#cx_varname.run_until_parked();
|
||||
drop(#cx_varname);
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
continue;
|
||||
@@ -277,6 +290,14 @@ fn generate_test_function(
|
||||
#cx_vars
|
||||
#inner_fn_name(#inner_fn_args);
|
||||
#cx_teardowns
|
||||
// Ideally we would only drop cancelled tasks, that way we could detect leaks due to task <-> entity
|
||||
// cycles as cancelled tasks will be dropped properly once they runnable gets run again
|
||||
// We can't do that in tests though, as well running tasks will run logic
|
||||
// and we might just not exit that way
|
||||
//
|
||||
// async-task does not give us the power to do this just yet though
|
||||
dispatcher.drain_tasks();
|
||||
drop(dispatcher);
|
||||
},
|
||||
#on_failure_fn_name,
|
||||
);
|
||||
|
||||
@@ -123,10 +123,7 @@ impl ProfilerWindow {
|
||||
fn begin_listen(cx: &mut Context<Self>) -> Task<()> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
loop {
|
||||
let data = cx
|
||||
.foreground_executor()
|
||||
.dispatcher
|
||||
.get_current_thread_timings();
|
||||
let data = cx.foreground_executor().get_current_thread_timings();
|
||||
|
||||
this.update(cx, |this: &mut ProfilerWindow, cx| {
|
||||
this.data = DataMode::Realtime(Some(data));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Borrow,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
sync::Arc,
|
||||
@@ -84,7 +83,7 @@ impl From<ExternalAgentServerName> for SharedString {
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for ExternalAgentServerName {
|
||||
impl std::borrow::Borrow<str> for ExternalAgentServerName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@@ -1150,10 +1150,9 @@ impl SettingsObserver {
|
||||
let mut user_tasks_file_rx =
|
||||
watch_config_file(cx.background_executor(), fs, file_path.clone());
|
||||
let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
|
||||
let weak_entry = cx.weak_entity();
|
||||
cx.spawn(async move |settings_observer, cx| {
|
||||
let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
|
||||
settings_observer.task_store.clone()
|
||||
settings_observer.task_store.downgrade()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
@@ -1181,7 +1180,7 @@ impl SettingsObserver {
|
||||
break;
|
||||
};
|
||||
|
||||
weak_entry
|
||||
settings_observer
|
||||
.update(cx, |_, cx| match result {
|
||||
Ok(()) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
|
||||
file_path.clone()
|
||||
@@ -1205,10 +1204,9 @@ impl SettingsObserver {
|
||||
let mut user_tasks_file_rx =
|
||||
watch_config_file(cx.background_executor(), fs, file_path.clone());
|
||||
let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
|
||||
let weak_entry = cx.weak_entity();
|
||||
cx.spawn(async move |settings_observer, cx| {
|
||||
let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
|
||||
settings_observer.task_store.clone()
|
||||
settings_observer.task_store.downgrade()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
@@ -1236,7 +1234,7 @@ impl SettingsObserver {
|
||||
break;
|
||||
};
|
||||
|
||||
weak_entry
|
||||
settings_observer
|
||||
.update(cx, |_, cx| match result {
|
||||
Ok(()) => cx.emit(SettingsObserverEvent::LocalDebugScenariosUpdated(Ok(
|
||||
file_path.clone(),
|
||||
|
||||
@@ -188,8 +188,9 @@ where
|
||||
|
||||
impl<F> Drop for Checked<F> {
|
||||
fn drop(&mut self) {
|
||||
assert!(
|
||||
self.id == thread_id(),
|
||||
assert_eq!(
|
||||
self.id,
|
||||
thread_id(),
|
||||
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
|
||||
self.location
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user