Compare commits

...

3 Commits

Author SHA1 Message Date
Thorsten Ball
4d41e6aa35 more debug 2024-09-06 18:32:43 +02:00
Thorsten Ball
5d0bf9904e debug project leak stuff 2024-09-06 17:50:39 +02:00
Thorsten Ball
1ca4cfe2f8 Fix Workspace references being leaked
We noticed that the `Workspace` was never released (along with the
`Project` and everything that comes along with that) when closing a
window.

After playing around with the LeakDetector and debugging with
`cx.on_release()` callbacks, we found two culprits: the inline assistant
and the outline panel.

Both held strong references to `View<Workspace>` after PR #16589 and
PR #16845.

This PR changes both references to `WeakView<Workspace>` which fixes the
leak but keeps the behaviour the same.

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-06 15:21:54 +02:00
16 changed files with 210 additions and 115 deletions

View File

@@ -341,7 +341,7 @@ impl AssistantPanel {
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().clone(),
workspace.project().downgrade(),
Default::default(),
None,
NewContext.boxed_clone(),

View File

@@ -134,8 +134,11 @@ impl InlineAssistant {
})
.detach();
let workspace = workspace.clone();
let workspace = workspace.downgrade();
cx.observe_global::<SettingsStore>(move |cx| {
let Some(workspace) = workspace.upgrade() else {
return;
};
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return;
};

View File

@@ -1054,12 +1054,28 @@ impl SerializableItem for Editor {
let buffer = buffer_task.await?;
pane.update(&mut cx, |_, cx| {
cx.new_view(|cx| {
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
})
});
// let weak = editor.model.downgrade();
// cx.spawn(|_, cx| async move {
// for i in 0..5 {
// println!("{i}/5: going to sleep for 5 seconds");
// cx.background_executor()
// .timer(std::time::Duration::from_secs(5))
// .await;
// }
// println!("done sleeping");
// weak.assert_released();
// })
// .detach();
editor
})
})
}

View File

@@ -13,7 +13,6 @@ workspace = true
[features]
default = []
test-support = [
"backtrace",
"collections/test-support",
"rand",
"util/test-support",
@@ -29,7 +28,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
async-task = "4.7"
backtrace = { version = "0.3", optional = true }
backtrace = { version = "0.3" }
blade-graphics = { workspace = true, optional = true }
blade-macros = { workspace = true, optional = true }
blade-util = { workspace = true, optional = true }

View File

@@ -362,6 +362,7 @@ impl AppContext {
let result = update(self);
if !self.flushing_effects && self.pending_updates == 1 {
self.flushing_effects = true;
println!("flushing effects");
self.flush_effects();
self.flushing_effects = false;
}
@@ -706,6 +707,7 @@ impl AppContext {
};
self.pending_effects.push_back(effect);
// If we're not in an update call; schedule one (somehow)
}
/// Called at the end of [`AppContext::update`] to complete any side effects
@@ -765,6 +767,7 @@ impl AppContext {
/// reference count has become zero. We invoke any release observers before dropping
/// each entity.
fn release_dropped_entities(&mut self) {
// println!("releasing dropped entities");
loop {
let dropped = self.entities.take_dropped();
if dropped.is_empty() {
@@ -1398,6 +1401,7 @@ impl Context for AppContext {
if window.removed {
cx.window_handles.remove(&handle.id);
cx.windows.remove(handle.id);
cx.flush_effects();
} else {
cx.windows
.get_mut(handle.id)

View File

@@ -17,7 +17,7 @@ use std::{
thread::panicking,
};
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
use collections::HashMap;
slotmap::new_key_type! {
@@ -57,7 +57,7 @@ pub(crate) struct EntityMap {
struct EntityRefCounts {
counts: SlotMap<EntityId, AtomicUsize>,
dropped_entity_ids: Vec<EntityId>,
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
leak_detector: LeakDetector,
}
@@ -68,7 +68,7 @@ impl EntityMap {
ref_counts: Arc::new(RwLock::new(EntityRefCounts {
counts: SlotMap::with_key(),
dropped_entity_ids: Vec::new(),
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
leak_detector: LeakDetector {
next_handle_id: 0,
entity_handles: HashMap::default(),
@@ -193,7 +193,7 @@ pub struct AnyModel {
pub(crate) entity_id: EntityId,
pub(crate) entity_type: TypeId,
entity_map: Weak<RwLock<EntityRefCounts>>,
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
handle_id: HandleId,
}
@@ -203,7 +203,7 @@ impl AnyModel {
entity_id: id,
entity_type,
entity_map: entity_map.clone(),
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
handle_id: entity_map
.upgrade()
.unwrap()
@@ -262,7 +262,7 @@ impl Clone for AnyModel {
entity_id: self.entity_id,
entity_type: self.entity_type,
entity_map: self.entity_map.clone(),
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
handle_id: self
.entity_map
.upgrade()
@@ -291,8 +291,9 @@ impl Drop for AnyModel {
}
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
if let Some(entity_map) = self.entity_map.upgrade() {
// println!("dropped model");
entity_map
.write()
.leak_detector
@@ -504,7 +505,7 @@ impl AnyWeakModel {
entity_id: self.entity_id,
entity_type: self.entity_type,
entity_map: self.entity_ref_counts.clone(),
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
handle_id: self
.entity_ref_counts
.upgrade()
@@ -516,7 +517,7 @@ impl AnyWeakModel {
}
/// Assert that model referenced by this weak handle has been released.
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
pub fn assert_released(&self) {
self.entity_ref_counts
.upgrade()
@@ -641,23 +642,23 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
}
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()));
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
pub(crate) struct HandleId {
id: u64, // id of the handle itself, not the pointed at object
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
pub(crate) struct LeakDetector {
next_handle_id: u64,
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
impl LeakDetector {
#[track_caller]
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
@@ -666,7 +667,7 @@ impl LeakDetector {
let handles = self.entity_handles.entry(entity_id).or_default();
handles.insert(
handle_id,
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
LEAK_BACKTRACE.then(|| backtrace::Backtrace::new_unresolved()),
);
handle_id
}
@@ -679,7 +680,7 @@ impl LeakDetector {
pub fn assert_released(&mut self, entity_id: EntityId) {
let handles = self.entity_handles.entry(entity_id).or_default();
if !handles.is_empty() {
for backtrace in handles.values_mut() {
for (_, backtrace) in handles {
if let Some(mut backtrace) = backtrace.take() {
backtrace.resolve();
eprintln!("Leaked handle: {:#?}", backtrace);

View File

@@ -926,7 +926,15 @@ impl<'a> WindowContext<'a> {
/// Close this window.
pub fn remove_window(&mut self) {
println!("remove window!");
self.window.removed = true;
// self.spawn(|cx| {
// for _ in 0..10 {
// cx.background_executor().timer(Duration::from_secs(1)).await;
// AsyncAppContext::update(&mut cx, |cx| cx.update(|cx| {})).ok();
// })
// .detach()
}
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus

View File

@@ -91,7 +91,7 @@ pub struct OutlinePanel {
fs: Arc<dyn Fs>,
width: Option<Pixels>,
project: Model<Project>,
workspace: View<Workspace>,
workspace: WeakView<Workspace>,
active: bool,
pinned: bool,
scroll_handle: UniformListScrollHandle,
@@ -607,7 +607,7 @@ impl OutlinePanel {
fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let project = workspace.project().clone();
let workspace_handle = cx.view().clone();
let workspace_handle = cx.view().downgrade();
let outline_panel = cx.new_view(|cx| {
let filter_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
@@ -865,7 +865,8 @@ impl OutlinePanel {
};
if let Some((offset, anchor)) = scroll_target {
self.workspace
let activate = self
.workspace
.update(cx, |workspace, cx| match self.active_item() {
Some(active_item) => {
workspace.activate_item(active_item.as_ref(), true, change_selection, cx)
@@ -873,21 +874,23 @@ impl OutlinePanel {
None => workspace.activate_item(&active_editor, true, change_selection, cx),
});
self.select_entry(entry.clone(), true, cx);
if change_selection {
active_editor.update(cx, |editor, cx| {
editor.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Top)),
cx,
|s| s.select_ranges(Some(anchor..anchor)),
);
});
active_editor.focus_handle(cx).focus(cx);
} else {
active_editor.update(cx, |editor, cx| {
editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, cx);
});
self.focus_handle.focus(cx);
if activate.is_ok() {
self.select_entry(entry.clone(), true, cx);
if change_selection {
active_editor.update(cx, |editor, cx| {
editor.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Top)),
cx,
|s| s.select_ranges(Some(anchor..anchor)),
);
});
active_editor.focus_handle(cx).focus(cx);
} else {
active_editor.update(cx, |editor, cx| {
editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, cx);
});
self.focus_handle.focus(cx);
}
}
}
}
@@ -3316,7 +3319,11 @@ impl OutlinePanel {
let buffer_search = self
.active_item()
.as_deref()
.and_then(|active_item| self.workspace.read(cx).pane_for(active_item))
.and_then(|active_item| {
self.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).pane_for(active_item))
})
.and_then(|pane| {
pane.read(cx)
.toolbar()
@@ -3567,8 +3574,10 @@ impl OutlinePanel {
) {
self.pinned = !self.pinned;
if !self.pinned {
if let Some((active_item, active_editor)) =
workspace_active_editor(self.workspace.read(cx), cx)
if let Some((active_item, active_editor)) = self
.workspace
.upgrade()
.and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
{
if self.should_replace_active_item(active_item.as_ref()) {
self.replace_active_editor(active_item, active_editor, cx);
@@ -3833,8 +3842,10 @@ impl Panel for OutlinePanel {
let old_active = outline_panel.active;
outline_panel.active = active;
if active && old_active != active {
if let Some((active_item, active_editor)) =
workspace_active_editor(outline_panel.workspace.read(cx), cx)
if let Some((active_item, active_editor)) = outline_panel
.workspace
.upgrade()
.and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
{
if outline_panel.should_replace_active_item(active_item.as_ref()) {
outline_panel.replace_active_editor(active_item, active_editor, cx);

View File

@@ -254,7 +254,12 @@ impl LspStore {
yarn,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
_subscription: cx.on_app_quit(Self::shutdown_language_servers),
_subscription: cx.on_release(|this, cx| {
println!("lsp store released");
cx.background_executor()
.spawn(this.shutdown_language_servers())
.detach();
}),
}
}
@@ -494,10 +499,8 @@ impl LspStore {
self.active_entry = active_entry;
}
fn shutdown_language_servers(
&mut self,
_cx: &mut ModelContext<Self>,
) -> impl Future<Output = ()> {
fn shutdown_language_servers(&mut self) -> impl Future<Output = ()> {
println!("shutdown language servers");
let shutdown_futures = self
.language_servers
.drain()
@@ -511,7 +514,9 @@ impl LspStore {
.collect::<Vec<_>>();
async move {
let count = shutdown_futures.len();
futures::future::join_all(shutdown_futures).await;
println!("done shutting down {} servers", count);
}
}

View File

@@ -658,6 +658,11 @@ impl Project {
});
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
cx.on_release(|_, _| {
println!("project. on release");
})
.detach();
Self {
buffer_ordered_messages_tx: tx,
collaborators: Default::default(),
@@ -1717,6 +1722,7 @@ impl Project {
}
pub fn close(&mut self, cx: &mut ModelContext<Self>) {
println!("Closing the project");
cx.emit(Event::Closed);
}

View File

@@ -68,15 +68,15 @@ impl AppSession {
let _subscriptions = vec![cx.on_app_quit(Self::app_will_quit)];
let _serialization_task = Some(cx.spawn(|_, cx| async move {
loop {
if let Some(windows) = cx.update(|cx| cx.window_stack()).ok().flatten() {
store_window_stack(windows).await;
}
// loop {
// if let Some(windows) = cx.update(|cx| cx.window_stack()).ok().flatten() {
// store_window_stack(windows).await;
// }
cx.background_executor()
.timer(Duration::from_millis(100))
.await;
}
// cx.background_executor()
// .timer(Duration::from_millis(100))
// .await;
// }
}));
Self {

View File

@@ -78,7 +78,7 @@ impl TerminalPanel {
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().clone(),
workspace.project().downgrade(),
Default::default(),
None,
NewTerminal.boxed_clone(),

View File

@@ -368,9 +368,13 @@ impl Dock {
cx.observe(&panel, |_, _, cx| cx.notify()),
cx.observe_global::<SettingsStore>({
let workspace = workspace.clone();
let panel = panel.clone();
let panel = panel.downgrade();
move |this, cx| {
let Some(panel) = panel.upgrade() else {
return;
};
let new_position = panel.read(cx).position(cx);
if new_position == this.position {
return;

View File

@@ -18,8 +18,8 @@ use gpui::{
AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, Div, DragMoveEvent, EntityId,
EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext, Model,
MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView,
WindowContext,
ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakModel,
WeakView, WindowContext,
};
use itertools::Itertools;
use parking_lot::Mutex;
@@ -255,7 +255,7 @@ pub struct Pane {
nav_history: NavHistory,
toolbar: View<Toolbar>,
pub(crate) workspace: WeakView<Workspace>,
project: Model<Project>,
project: WeakModel<Project>,
drag_split_direction: Option<SplitDirection>,
can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>>,
custom_drop_handle:
@@ -337,7 +337,7 @@ impl EventEmitter<Event> for Pane {}
impl Pane {
pub fn new(
workspace: WeakView<Workspace>,
project: Model<Project>,
project: WeakModel<Project>,
next_timestamp: Arc<AtomicUsize>,
can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>>,
double_click_dispatch_action: Box<dyn Action>,
@@ -802,14 +802,15 @@ impl Pane {
) {
if item.is_singleton(cx) {
if let Some(&entry_id) = item.project_entry_ids(cx).first() {
let project = self.project.read(cx);
if let Some(project_path) = project.path_for_entry(entry_id, cx) {
let abs_path = project.absolute_path(&project_path, cx);
self.nav_history
.0
.lock()
.paths_by_item
.insert(item.item_id(), (project_path, abs_path));
if let Some(project) = self.project.upgrade().map(|project| project.read(cx)) {
if let Some(project_path) = project.path_for_entry(entry_id, cx) {
let abs_path = project.absolute_path(&project_path, cx);
self.nav_history
.0
.lock()
.paths_by_item
.insert(item.item_id(), (project_path, abs_path));
}
}
}
}
@@ -1811,7 +1812,7 @@ impl Pane {
let icon_color = if ItemSettings::get_global(cx).git_status {
project_path
.as_ref()
.and_then(|path| self.project.read(cx).entry_for_path(path, cx))
.and_then(|path| self.project.upgrade()?.read(cx).entry_for_path(path, cx))
.map(|entry| {
Self::git_aware_icon_color(entry.git_status, entry.is_ignored, is_active)
})
@@ -2057,11 +2058,13 @@ impl Pane {
entry_id: Some(entry_id),
})),
cx.handler_for(&pane, move |pane, cx| {
pane.project.update(cx, |_, cx| {
cx.emit(project::Event::RevealInProjectPanel(
ProjectEntryId::from_proto(entry_id),
))
});
pane.project
.update(cx, |_, cx| {
cx.emit(project::Event::RevealInProjectPanel(
ProjectEntryId::from_proto(entry_id),
))
})
.log_err();
}),
)
.when_some(parent_abs_path, |menu, parent_abs_path| {
@@ -2605,9 +2608,11 @@ impl Render for Pane {
.map(ProjectEntryId::from_proto)
.or_else(|| pane.active_item()?.project_entry_ids(cx).first().copied());
if let Some(entry_id) = entry_id {
pane.project.update(cx, |_, cx| {
cx.emit(project::Event::RevealInProjectPanel(entry_id))
});
pane.project
.update(cx, |_, cx| {
cx.emit(project::Event::RevealInProjectPanel(entry_id))
})
.log_err();
}
}),
)
@@ -2615,7 +2620,11 @@ impl Render for Pane {
pane.child(self.render_tab_bar(cx))
})
.child({
let has_worktrees = self.project.read(cx).worktrees(cx).next().is_some();
let has_worktrees = self
.project
.upgrade()
.and_then(|project| project.read(cx).worktrees(cx).next())
.is_some();
// main content
div()
.flex_1()

View File

@@ -873,7 +873,7 @@ impl Workspace {
let center_pane = cx.new_view(|cx| {
Pane::new(
weak_handle.clone(),
project.clone(),
project.downgrade(),
pane_history_timestamp.clone(),
None,
NewFile.boxed_clone(),
@@ -1004,6 +1004,7 @@ impl Workspace {
cx.on_release(|this, window, cx| {
this.app_state.workspace_store.update(cx, |store, _| {
let window = window.downcast::<Self>().unwrap();
println!("workspace released!");
store.workspaces.remove(&window);
})
}),
@@ -1634,17 +1635,39 @@ impl Workspace {
}
pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
let project = self.project.downgrade();
// let handle = cx.model();
// let weak = self.weak_handle();
let prepare = self.prepare_to_close(CloseIntent::CloseWindow, cx);
let window = cx.window_handle();
cx.spawn(|_, mut cx| async move {
if prepare.await? {
window.update(&mut cx, |_, cx| {
println!("remove window");
cx.remove_window();
})?;
cx.spawn(|cx| async move {
println!("going to sleep for 8 sec");
cx.background_executor().timer(Duration::from_secs(8)).await;
println!("slept for 8 sec");
println!("asserting the project weak handle is released");
project.assert_released();
})
.detach();
// for _ in 0..10 {
// cx.background_executor().timer(Duration::from_secs(1)).await;
// dbg!("refreshing");
// AsyncAppContext::update(&mut cx, |cx| cx.new_model(|cx| ())).ok();
// }
println!("loop done");
}
anyhow::Ok(())
})
.detach_and_log_err(cx)
.detach_and_log_err(cx);
}
pub fn prepare_to_close(
@@ -2408,7 +2431,7 @@ impl Workspace {
let pane = cx.new_view(|cx| {
Pane::new(
self.weak_handle(),
self.project.clone(),
self.project.downgrade(),
self.pane_history_timestamp.clone(),
None,
NewFile.boxed_clone(),
@@ -4020,6 +4043,7 @@ impl Workspace {
}
fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
println!("--- serialize workspace internal start ---");
let Some(database_id) = self.database_id() else {
return Task::ready(());
};
@@ -4161,7 +4185,10 @@ impl Workspace {
session_id: self.session_id.clone(),
window_id: Some(cx.window_handle().window_id().as_u64()),
};
return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
return cx.spawn(|_| async move {
persistence::DB.save_workspace(serialized_workspace).await;
println!("--- serialize workspace internal END ---")
});
}
Task::ready(())
}
@@ -4177,6 +4204,7 @@ impl Workspace {
let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
while let Some(items_received) = serializable_items.next().await {
println!("serializing item!");
let unique_items =
items_received
.into_iter()
@@ -4199,6 +4227,7 @@ impl Workspace {
cx.background_executor().timer(THROTTLE_TIME).await;
}
println!("done serializing items");
Ok(())
}

View File

@@ -251,47 +251,47 @@ pub fn initialize_workspace(
let prompt_builder = prompt_builder.clone();
cx.spawn(|workspace_handle, mut cx| async move {
let assistant_panel =
assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone());
// // let assistant_panel =
// // assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone());
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
// // let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone());
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
let channels_panel =
collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
let chat_panel =
collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
workspace_handle.clone(),
cx.clone(),
);
// let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
// // let channels_panel =
// // collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
// // let chat_panel =
// // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
// let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
// workspace_handle.clone(),
// cx.clone(),
// );
let (
project_panel,
// project_panel,
outline_panel,
terminal_panel,
assistant_panel,
channels_panel,
chat_panel,
notification_panel,
// terminal_panel,
// assistant_panel,
// channels_panel,
// chat_panel,
// notification_panel,
) = futures::try_join!(
project_panel,
// project_panel,
outline_panel,
terminal_panel,
assistant_panel,
channels_panel,
chat_panel,
notification_panel,
// terminal_panel,
// assistant_panel,
// channels_panel,
// chat_panel,
// notification_panel,
)?;
workspace_handle.update(&mut cx, |workspace, cx| {
workspace.add_panel(assistant_panel, cx);
workspace.add_panel(project_panel, cx);
// workspace.add_panel(assistant_panel, cx);
// workspace.add_panel(project_panel, cx);
workspace.add_panel(outline_panel, cx);
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(channels_panel, cx);
workspace.add_panel(chat_panel, cx);
workspace.add_panel(notification_panel, cx);
// workspace.add_panel(terminal_panel, cx);
// workspace.add_panel(channels_panel, cx);
// workspace.add_panel(chat_panel, cx);
// workspace.add_panel(notification_panel, cx);
cx.focus_self();
})
})