diff --git a/Cargo.lock b/Cargo.lock
index 2ae5f44ac976fbc08eccf8a8896c18d3bb982c5a..95aa65c06ceeb74ef03443b62912b3ee60fceb46 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4942,7 +4942,6 @@ checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
name = "dev_container"
version = "0.1.0"
dependencies = [
- "fs",
"futures 0.3.31",
"gpui",
"http 1.3.1",
@@ -4952,12 +4951,10 @@ dependencies = [
"node_runtime",
"paths",
"picker",
- "project",
"serde",
"serde_json",
"settings",
"smol",
- "theme",
"ui",
"util",
"workspace",
@@ -8495,6 +8492,7 @@ dependencies = [
"fuzzy",
"gpui",
"language",
+ "platform_title_bar",
"project",
"serde_json",
"serde_json_lenient",
@@ -12382,7 +12380,6 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
name = "platform_title_bar"
version = "0.1.0"
dependencies = [
- "feature_flags",
"gpui",
"settings",
"smallvec",
@@ -15349,30 +15346,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-[[package]]
-name = "sidebar"
-version = "0.1.0"
-dependencies = [
- "acp_thread",
- "agent_ui",
- "db",
- "editor",
- "feature_flags",
- "fs",
- "fuzzy",
- "gpui",
- "picker",
- "project",
- "recent_projects",
- "serde_json",
- "settings",
- "theme",
- "ui",
- "ui_input",
- "util",
- "workspace",
-]
-
[[package]]
name = "signal-hook"
version = "0.3.18"
@@ -17252,7 +17225,6 @@ dependencies = [
"cloud_api_types",
"collections",
"db",
- "feature_flags",
"git_ui",
"gpui",
"http_client",
@@ -21138,7 +21110,6 @@ dependencies = [
"settings_profile_selector",
"settings_ui",
"shellexpand 2.1.2",
- "sidebar",
"smol",
"snippet_provider",
"snippets_ui",
diff --git a/Cargo.toml b/Cargo.toml
index b0935c94e9bba08da262bdaabf4385ec9419578c..b641d42e4f4c76bde644a0b0795f3f9256a2962b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -155,7 +155,6 @@ members = [
"crates/schema_generator",
"crates/search",
"crates/session",
- "crates/sidebar",
"crates/settings",
"crates/settings_content",
"crates/settings_json",
@@ -397,7 +396,6 @@ rules_library = { path = "crates/rules_library" }
scheduler = { path = "crates/scheduler" }
search = { path = "crates/search" }
session = { path = "crates/session" }
-sidebar = { path = "crates/sidebar" }
settings = { path = "crates/settings" }
settings_content = { path = "crates/settings_content" }
settings_json = { path = "crates/settings_json" }
@@ -857,7 +855,6 @@ refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
session = { codegen-units = 1 }
-sidebar = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }
story = { codegen-units = 1 }
diff --git a/assets/icons/workspace_nav_closed.svg b/assets/icons/workspace_nav_closed.svg
deleted file mode 100644
index ed1fce52d6826a4d10299f331358ff84e4caa973..0000000000000000000000000000000000000000
--- a/assets/icons/workspace_nav_closed.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/assets/icons/workspace_nav_open.svg b/assets/icons/workspace_nav_open.svg
deleted file mode 100644
index 464b6aac73c2aeaa9463a805aabc4559377bbfd3..0000000000000000000000000000000000000000
--- a/assets/icons/workspace_nav_open.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index d877cfb41206087f555e47e01dccffeb9357b2c8..43c58411a6c4b4140a59c55a24d37716f0ab1ad3 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -596,7 +596,6 @@
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
- "ctrl-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
"ctrl-alt-y": "workspace::ToggleAllDocks",
"ctrl-alt-0": "workspace::ResetActiveDockSize",
// For 0px parameter, uses UI font size value.
@@ -656,13 +655,6 @@
"ctrl-w": "workspace::CloseActiveDock",
},
},
- {
- "context": "WorkspaceSidebar",
- "use_key_equivalents": true,
- "bindings": {
- "ctrl-n": "multi_workspace::NewWorkspaceInWindow",
- },
- },
{
"context": "Workspace && debugger_running",
"bindings": {
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 19ce418bf13ef28e37149b3fc9dc3644f0fc782d..813f7442b6cb3ce93a130be01e0043c3ca025d9a 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -657,7 +657,6 @@
"cmd-alt-b": "workspace::ToggleRightDock",
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
- "cmd-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
"alt-cmd-y": "workspace::ToggleAllDocks",
// For 0px parameter, uses UI font size value.
"ctrl-alt-0": "workspace::ResetActiveDockSize",
@@ -717,13 +716,6 @@
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
},
},
- {
- "context": "WorkspaceSidebar",
- "use_key_equivalents": true,
- "bindings": {
- "cmd-n": "multi_workspace::NewWorkspaceInWindow",
- },
- },
{
"context": "Workspace && debugger_running",
"use_key_equivalents": true,
diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json
index 5e0cbdc8afabf0c4e9901a256f44d653e294b02c..ae00aff39ef3529fa906f18d3a9a28e8fa6b688c 100644
--- a/assets/keymaps/default-windows.json
+++ b/assets/keymaps/default-windows.json
@@ -591,7 +591,6 @@
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
- "ctrl-alt-j": "multi_workspace::ToggleWorkspaceSidebar",
"ctrl-shift-y": "workspace::ToggleAllDocks",
"alt-r": "workspace::ResetActiveDockSize",
// For 0px parameter, uses UI font size value.
@@ -660,13 +659,6 @@
"f5": "debugger::Continue",
},
},
- {
- "context": "WorkspaceSidebar",
- "use_key_equivalents": true,
- "bindings": {
- "ctrl-n": "multi_workspace::NewWorkspaceInWindow",
- },
- },
{
"context": "ApplicationMenu",
"use_key_equivalents": true,
diff --git a/crates/agent_ui/src/acp.rs b/crates/agent_ui/src/acp.rs
index f76e64b557e7ee2ec6054bd0fab0afc36b201e2c..904c9a6c7b7e383d09b54f58115be2303ef8754a 100644
--- a/crates/agent_ui/src/acp.rs
+++ b/crates/agent_ui/src/acp.rs
@@ -5,7 +5,7 @@ mod mode_selector;
mod model_selector;
mod model_selector_popover;
mod thread_history;
-pub(crate) mod thread_view;
+mod thread_view;
pub use mode_selector::ModeSelector;
pub use model_selector::AcpModelSelector;
diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs
index faea22c45e32ba53f22367f89d8abe292f6e0753..7c9966295483d5c0b0b5586b7d020c98db50f25f 100644
--- a/crates/agent_ui/src/acp/message_editor.rs
+++ b/crates/agent_ui/src/acp/message_editor.rs
@@ -815,13 +815,8 @@ impl MessageEditor {
}
if self.prompt_capabilities.borrow().image
- && let Some(task) = paste_images_as_context(
- self.editor.clone(),
- self.mention_set.clone(),
- self.workspace.clone(),
- window,
- cx,
- )
+ && let Some(task) =
+ paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
{
task.detach();
return;
@@ -1089,7 +1084,6 @@ impl MessageEditor {
let editor = self.editor.clone();
let mention_set = self.mention_set.clone();
- let workspace = self.workspace.clone();
let paths_receiver = cx.prompt_for_paths(gpui::PathPromptOptions {
files: true,
@@ -1140,14 +1134,7 @@ impl MessageEditor {
images.push(gpui::Image::from_bytes(format, content));
}
- crate::mention_set::insert_images_as_context(
- images,
- editor,
- mention_set,
- workspace,
- cx,
- )
- .await;
+ crate::mention_set::insert_images_as_context(images, editor, mention_set, cx).await;
Ok(())
})
.detach_and_log_err(cx);
diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs
index 3dfdeccf7ec9f03b84752101502c708ecb08ac88..e294a08d14c2c993e4fb73e05e0e3eb001860c0e 100644
--- a/crates/agent_ui/src/acp/thread_view.rs
+++ b/crates/agent_ui/src/acp/thread_view.rs
@@ -57,9 +57,7 @@ use ui::{
};
use util::defer;
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
-use workspace::{
- CollaboratorId, MultiWorkspace, NewTerminal, Toast, Workspace, notifications::NotificationId,
-};
+use workspace::{CollaboratorId, NewTerminal, Toast, Workspace, notifications::NotificationId};
use zed_actions::agent::{Chat, ToggleModelSelector};
use zed_actions::assistant::OpenRulesLibrary;
@@ -1988,30 +1986,9 @@ impl AcpServerView {
self.show_notification(caption, icon, window, cx);
}
- fn agent_is_visible(&self, window: &Window, cx: &App) -> bool {
- if window.is_window_active() {
- let workspace_is_foreground = window
- .root::()
- .flatten()
- .and_then(|mw| {
- let mw = mw.read(cx);
- self.workspace.upgrade().map(|ws| mw.workspace() == &ws)
- })
- .unwrap_or(true);
-
- if workspace_is_foreground {
- if let Some(workspace) = self.workspace.upgrade() {
- return AgentPanel::is_visible(&workspace, cx);
- }
- }
- }
-
- false
- }
-
fn play_notification_sound(&self, window: &Window, cx: &mut App) {
let settings = AgentSettings::get_global(cx);
- if settings.play_sound_when_agent_done && !self.agent_is_visible(window, cx) {
+ if settings.play_sound_when_agent_done && !window.is_window_active() {
Audio::play_sound(Sound::AgentDone, cx);
}
}
@@ -2029,7 +2006,14 @@ impl AcpServerView {
let settings = AgentSettings::get_global(cx);
- let should_notify = !self.agent_is_visible(window, cx);
+ let window_is_inactive = !window.is_window_active();
+ let panel_is_hidden = self
+ .workspace
+ .upgrade()
+ .map(|workspace| AgentPanel::is_hidden(&workspace, cx))
+ .unwrap_or(true);
+
+ let should_notify = window_is_inactive || panel_is_hidden;
if !should_notify {
return;
@@ -2092,22 +2076,19 @@ impl AcpServerView {
.push(cx.subscribe_in(&pop_up, window, {
|this, _, event, window, cx| match event {
AgentNotificationEvent::Accepted => {
- let Some(handle) = window.window_handle().downcast::()
- else {
- log::error!("root view should be a MultiWorkspace");
- return;
- };
+ let handle = window.window_handle();
cx.activate(true);
let workspace_handle = this.workspace.clone();
+ // If there are multiple Zed windows, activate the correct one.
cx.defer(move |cx| {
handle
- .update(cx, |multi_workspace, window, cx| {
+ .update(cx, |_view, window, _cx| {
window.activate_window();
+
if let Some(workspace) = workspace_handle.upgrade() {
- multi_workspace.activate(workspace.clone(), cx);
- workspace.update(cx, |workspace, cx| {
+ workspace.update(_cx, |workspace, cx| {
workspace.focus_panel::(window, cx);
});
}
@@ -2132,12 +2113,12 @@ impl AcpServerView {
.push({
let pop_up_weak = pop_up.downgrade();
- cx.observe_window_activation(window, move |this, window, cx| {
- if this.agent_is_visible(window, cx)
+ cx.observe_window_activation(window, move |_, window, cx| {
+ if window.is_window_active()
&& let Some(pop_up) = pop_up_weak.upgrade()
{
- pop_up.update(cx, |notification, cx| {
- notification.dismiss(cx);
+ pop_up.update(cx, |_, cx| {
+ cx.emit(AgentNotificationEvent::Dismissed);
});
}
})
@@ -2388,7 +2369,6 @@ pub(crate) mod tests {
use action_log::ActionLog;
use agent::{AgentTool, EditFileTool, FetchTool, TerminalTool, ToolPermissionContext};
use agent_client_protocol::SessionId;
- use assistant_text_thread::TextThreadStore;
use editor::MultiBufferOffset;
use fs::FakeFs;
use gpui::{EventEmitter, TestAppContext, VisualTestContext};
@@ -2398,7 +2378,7 @@ pub(crate) mod tests {
use std::any::Any;
use std::path::Path;
use std::rc::Rc;
- use workspace::{Item, MultiWorkspace};
+ use workspace::Item;
use super::*;
@@ -2698,138 +2678,6 @@ pub(crate) mod tests {
);
}
- #[gpui::test]
- async fn test_notification_when_workspace_is_background_in_multi_workspace(
- cx: &mut TestAppContext,
- ) {
- init_test(cx);
-
- // Enable multi-workspace feature flag and init globals needed by AgentPanel
- let fs = FakeFs::new(cx.executor());
-
- cx.update(|cx| {
- cx.update_flags(true, vec!["agent-v2".to_string()]);
- agent::ThreadStore::init_global(cx);
- language_model::LanguageModelRegistry::test(cx);
- ::set_global(fs.clone(), cx);
- });
-
- let project1 = Project::test(fs.clone(), [], cx).await;
-
- // Create a MultiWorkspace window with one workspace
- let multi_workspace_handle =
- cx.add_window(|window, cx| MultiWorkspace::test_new(project1.clone(), window, cx));
-
- // Get workspace 1 (the initial workspace)
- let workspace1 = multi_workspace_handle
- .read_with(cx, |mw, _cx| mw.workspace().clone())
- .unwrap();
-
- let cx = &mut VisualTestContext::from_window(multi_workspace_handle.into(), cx);
-
- workspace1.update_in(cx, |workspace, window, cx| {
- let text_thread_store =
- cx.new(|cx| TextThreadStore::fake(workspace.project().clone(), cx));
- let panel =
- cx.new(|cx| crate::AgentPanel::new(workspace, text_thread_store, None, window, cx));
- workspace.add_panel(panel, window, cx);
-
- // Open the dock and activate the agent panel so it's visible
- workspace.focus_panel::(window, cx);
- });
-
- cx.run_until_parked();
-
- cx.read(|cx| {
- assert!(
- crate::AgentPanel::is_visible(&workspace1, cx),
- "AgentPanel should be visible in workspace1's dock"
- );
- });
-
- // Set up thread view in workspace 1
- let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|window, cx| cx.new(|cx| AcpThreadHistory::new(None, window, cx)));
-
- let agent = StubAgentServer::default_response();
- let thread_view = cx.update(|window, cx| {
- cx.new(|cx| {
- AcpServerView::new(
- Rc::new(agent),
- None,
- None,
- workspace1.downgrade(),
- project1.clone(),
- Some(thread_store),
- None,
- history,
- window,
- cx,
- )
- })
- });
- cx.run_until_parked();
-
- let message_editor = message_editor(&thread_view, cx);
- message_editor.update_in(cx, |editor, window, cx| {
- editor.set_text("Hello", window, cx);
- });
-
- // Create a second workspace and switch to it.
- // This makes workspace1 the "background" workspace.
- let project2 = Project::test(fs, [], cx).await;
- multi_workspace_handle
- .update(cx, |mw, window, cx| {
- let workspace2 = cx.new(|cx| Workspace::test_new(project2, window, cx));
- mw.activate(workspace2, cx);
- })
- .unwrap();
-
- cx.run_until_parked();
-
- // Verify workspace1 is no longer the active workspace
- multi_workspace_handle
- .read_with(cx, |mw, _cx| {
- assert_eq!(mw.active_workspace_index(), 1);
- assert_ne!(mw.workspace(), &workspace1);
- })
- .unwrap();
-
- // Window is active, agent panel is visible in workspace1, but workspace1
- // is in the background. The notification should show because the user
- // can't actually see the agent panel.
- active_thread(&thread_view, cx).update_in(cx, |view, window, cx| view.send(window, cx));
-
- cx.run_until_parked();
-
- assert!(
- cx.windows()
- .iter()
- .any(|window| window.downcast::().is_some()),
- "Expected notification when workspace is in background within MultiWorkspace"
- );
-
- // Also verify: clicking "View Panel" should switch to workspace1.
- cx.windows()
- .iter()
- .find_map(|window| window.downcast::())
- .unwrap()
- .update(cx, |window, _, cx| window.accept(cx))
- .unwrap();
-
- cx.run_until_parked();
-
- multi_workspace_handle
- .read_with(cx, |mw, _cx| {
- assert_eq!(
- mw.workspace(),
- &workspace1,
- "Expected workspace1 to become the active workspace after accepting notification"
- );
- })
- .unwrap();
- }
-
#[gpui::test]
async fn test_notification_respects_never_setting(cx: &mut TestAppContext) {
init_test(cx);
@@ -2992,18 +2840,18 @@ pub(crate) mod tests {
}
}
- pub(crate) struct StubAgentServer {
+ struct StubAgentServer {
connection: C,
}
impl StubAgentServer {
- pub(crate) fn new(connection: C) -> Self {
+ fn new(connection: C) -> Self {
Self { connection }
}
}
impl StubAgentServer {
- pub(crate) fn default_response() -> Self {
+ fn default_response() -> Self {
let conn = StubAgentConnection::new();
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk::new("Default response".into()),
diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs
index bb00be46bad837a3b2242e885905709fbe38868c..850822679d2828b96ba6218c4d48e570764d6de6 100644
--- a/crates/agent_ui/src/agent_diff.rs
+++ b/crates/agent_ui/src/agent_diff.rs
@@ -1352,10 +1352,10 @@ impl AgentDiff {
self.update_reviewing_editors(workspace, window, cx);
}
}
- AcpThreadEvent::Stopped => {
- self.update_reviewing_editors(workspace, window, cx);
- }
- AcpThreadEvent::Error | AcpThreadEvent::LoadError(_) | AcpThreadEvent::Refusal => {
+ AcpThreadEvent::Stopped
+ | AcpThreadEvent::Error
+ | AcpThreadEvent::LoadError(_)
+ | AcpThreadEvent::Refusal => {
self.update_reviewing_editors(workspace, window, cx);
}
AcpThreadEvent::TitleUpdated
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index d5602b0d0913e4f22e0955b7561c7806c5f181bf..bd9c31a983b723c222987544561cea82a97bad2b 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -81,50 +81,10 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
const RECENTLY_UPDATED_MENU_LIMIT: usize = 6;
const DEFAULT_THREAD_TITLE: &str = "New Thread";
-fn read_serialized_panel(workspace_id: workspace::WorkspaceId) -> Option {
- let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY);
- let key = i64::from(workspace_id).to_string();
- scope
- .read(&key)
- .log_err()
- .flatten()
- .and_then(|json| serde_json::from_str::(&json).log_err())
-}
-
-async fn save_serialized_panel(
- workspace_id: workspace::WorkspaceId,
- panel: SerializedAgentPanel,
-) -> Result<()> {
- let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY);
- let key = i64::from(workspace_id).to_string();
- scope.write(key, serde_json::to_string(&panel)?).await?;
- Ok(())
-}
-
-/// Migration: reads the original single-panel format stored under the
-/// `"agent_panel"` KVP key before per-workspace keying was introduced.
-fn read_legacy_serialized_panel() -> Option {
- KEY_VALUE_STORE
- .read_kvp(AGENT_PANEL_KEY)
- .log_err()
- .flatten()
- .and_then(|json| serde_json::from_str::(&json).log_err())
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Serialize, Deserialize, Debug)]
struct SerializedAgentPanel {
width: Option,
selected_agent: Option,
- #[serde(default)]
- last_active_thread: Option,
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-struct SerializedActiveThread {
- session_id: String,
- agent_type: AgentType,
- title: Option,
- cwd: Option,
}
pub fn init(cx: &mut App) {
@@ -468,7 +428,6 @@ pub struct AgentPanel {
focus_handle: FocusHandle,
active_view: ActiveView,
previous_view: Option,
- _active_view_observation: Option,
new_thread_menu_handle: PopoverMenuHandle,
agent_panel_menu_handle: PopoverMenuHandle,
agent_navigation_menu_handle: PopoverMenuHandle,
@@ -485,45 +444,19 @@ pub struct AgentPanel {
}
impl AgentPanel {
- fn serialize(&mut self, cx: &mut App) {
- let workspace_id = self
- .workspace
- .read_with(cx, |workspace, _| workspace.database_id())
- .ok()
- .flatten();
-
- let Some(workspace_id) = workspace_id else {
- return;
- };
-
+ fn serialize(&mut self, cx: &mut Context) {
let width = self.width;
let selected_agent = self.selected_agent.clone();
-
- let last_active_thread = self.active_agent_thread(cx).map(|thread| {
- let thread = thread.read(cx);
- let title = thread.title();
- SerializedActiveThread {
- session_id: thread.session_id().0.to_string(),
- agent_type: self.selected_agent.clone(),
- title: if title.as_ref() != DEFAULT_THREAD_TITLE {
- Some(title.to_string())
- } else {
- None
- },
- cwd: None,
- }
- });
-
self.pending_serialization = Some(cx.background_spawn(async move {
- save_serialized_panel(
- workspace_id,
- SerializedAgentPanel {
- width,
- selected_agent: Some(selected_agent),
- last_active_thread,
- },
- )
- .await?;
+ KEY_VALUE_STORE
+ .write_kvp(
+ AGENT_PANEL_KEY.into(),
+ serde_json::to_string(&SerializedAgentPanel {
+ width,
+ selected_agent: Some(selected_agent),
+ })?,
+ )
+ .await?;
anyhow::Ok(())
}));
}
@@ -539,18 +472,16 @@ impl AgentPanel {
Ok(prompt_store) => prompt_store.await.ok(),
Err(_) => None,
};
- let workspace_id = workspace
- .read_with(cx, |workspace, _| workspace.database_id())
- .ok()
- .flatten();
-
- let serialized_panel = cx
- .background_spawn(async move {
- workspace_id
- .and_then(read_serialized_panel)
- .or_else(read_legacy_serialized_panel)
- })
- .await;
+ let serialized_panel = if let Some(panel) = cx
+ .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
+ .await
+ .log_err()
+ .flatten()
+ {
+ serde_json::from_str::(&panel).log_err()
+ } else {
+ None
+ };
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
let text_thread_store = workspace
@@ -569,30 +500,15 @@ impl AgentPanel {
let panel =
cx.new(|cx| Self::new(workspace, text_thread_store, prompt_store, window, cx));
- if let Some(serialized_panel) = &serialized_panel {
+ if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|w| w.round());
- if let Some(selected_agent) = serialized_panel.selected_agent.clone() {
+ if let Some(selected_agent) = serialized_panel.selected_agent {
panel.selected_agent = selected_agent;
}
cx.notify();
});
}
-
- if let Some(thread_info) = serialized_panel.and_then(|p| p.last_active_thread) {
- let agent_type = thread_info.agent_type.clone();
- let session_info = AgentSessionInfo {
- session_id: acp::SessionId::new(thread_info.session_id),
- cwd: thread_info.cwd,
- title: thread_info.title.map(SharedString::from),
- updated_at: None,
- meta: None,
- };
- panel.update(cx, |panel, cx| {
- panel.selected_agent = agent_type;
- panel.load_agent_thread(session_info, window, cx);
- });
- }
panel
})?;
@@ -600,7 +516,7 @@ impl AgentPanel {
})
}
- pub(crate) fn new(
+ fn new(
workspace: &Workspace,
text_thread_store: Entity,
prompt_store: Option>,
@@ -730,7 +646,6 @@ impl AgentPanel {
focus_handle: cx.focus_handle(),
context_server_registry,
previous_view: None,
- _active_view_observation: None,
new_thread_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
agent_navigation_menu_handle: PopoverMenuHandle::default(),
@@ -799,7 +714,7 @@ impl AgentPanel {
&self.context_server_registry
}
- pub fn is_visible(workspace: &Entity, cx: &App) -> bool {
+ pub fn is_hidden(workspace: &Entity, cx: &App) -> bool {
let workspace_read = workspace.read(cx);
workspace_read
@@ -807,13 +722,15 @@ impl AgentPanel {
.map(|panel| {
let panel_id = Entity::entity_id(&panel);
- workspace_read.all_docks().iter().any(|dock| {
+ let is_visible = workspace_read.all_docks().iter().any(|dock| {
dock.read(cx)
.visible_panel()
.is_some_and(|visible_panel| visible_panel.panel_id() == panel_id)
- })
+ });
+
+ !is_visible
})
- .unwrap_or(false)
+ .unwrap_or(true)
}
pub(crate) fn active_thread_view(&self) -> Option<&Entity> {
@@ -1106,7 +1023,6 @@ impl AgentPanel {
ActiveView::Configuration | ActiveView::History { .. } => {
if let Some(previous_view) = self.previous_view.take() {
self.active_view = previous_view;
- cx.emit(AgentPanelEvent::ActiveViewChanged);
match &self.active_view {
ActiveView::AgentThread { thread_view } => {
@@ -1503,7 +1419,7 @@ impl AgentPanel {
}
}
- pub fn active_agent_thread(&self, cx: &App) -> Option> {
+ pub(crate) fn active_agent_thread(&self, cx: &App) -> Option> {
match &self.active_view {
ActiveView::AgentThread { thread_view, .. } => thread_view
.read(cx)
@@ -1559,21 +1475,9 @@ impl AgentPanel {
self.active_view = new_view;
}
- self._active_view_observation = match &self.active_view {
- ActiveView::AgentThread { thread_view } => {
- Some(cx.observe(thread_view, |this, _, cx| {
- cx.emit(AgentPanelEvent::ActiveViewChanged);
- this.serialize(cx);
- cx.notify();
- }))
- }
- _ => None,
- };
-
if focus {
self.focus_handle(cx).focus(window, cx);
}
- cx.emit(AgentPanelEvent::ActiveViewChanged);
}
fn populate_recently_updated_menu_section(
@@ -1846,12 +1750,7 @@ fn agent_panel_dock_position(cx: &App) -> DockPosition {
AgentSettings::get_global(cx).dock.into()
}
-pub enum AgentPanelEvent {
- ActiveViewChanged,
-}
-
impl EventEmitter for AgentPanel {}
-impl EventEmitter for AgentPanel {}
impl Panel for AgentPanel {
fn persistent_name() -> &'static str {
@@ -1894,14 +1793,7 @@ impl Panel for AgentPanel {
DockPosition::Left | DockPosition::Right => self.width = size,
DockPosition::Bottom => self.height = size,
}
- let this = cx.weak_entity();
- cx.defer(move |cx| {
- if let Some(this) = this.upgrade() {
- this.update(cx, |this, cx| {
- this.serialize(cx);
- });
- }
- });
+ self.serialize(cx);
cx.notify();
}
@@ -3392,151 +3284,3 @@ impl AgentPanel {
self.active_thread_view()
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::acp::thread_view::tests::{StubAgentServer, init_test};
- use assistant_text_thread::TextThreadStore;
- use feature_flags::FeatureFlagAppExt;
- use fs::FakeFs;
- use gpui::{TestAppContext, VisualTestContext};
- use project::Project;
- use workspace::{MultiWorkspace, Workspace};
-
- #[gpui::test]
- async fn test_active_thread_serialize_and_load_round_trip(cx: &mut TestAppContext) {
- init_test(cx);
- cx.update(|cx| {
- cx.update_flags(true, vec!["agent-v2".to_string()]);
- agent::ThreadStore::init_global(cx);
- language_model::LanguageModelRegistry::test(cx);
- });
-
- // --- Create a MultiWorkspace window with two workspaces ---
- let fs = FakeFs::new(cx.executor());
- let project_a = Project::test(fs.clone(), [], cx).await;
- let project_b = Project::test(fs, [], cx).await;
-
- let multi_workspace =
- cx.add_window(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
-
- let workspace_a = multi_workspace
- .read_with(cx, |multi_workspace, _cx| {
- multi_workspace.workspace().clone()
- })
- .unwrap();
-
- let workspace_b = multi_workspace
- .update(cx, |multi_workspace, window, cx| {
- let workspace = cx.new(|cx| Workspace::test_new(project_b.clone(), window, cx));
- multi_workspace.activate(workspace.clone(), cx);
- workspace
- })
- .unwrap();
-
- workspace_a.update(cx, |workspace, _cx| {
- workspace.set_random_database_id();
- });
- workspace_b.update(cx, |workspace, _cx| {
- workspace.set_random_database_id();
- });
-
- let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
-
- // --- Set up workspace A: width=300, with an active thread ---
- let panel_a = workspace_a.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_a.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
- });
-
- panel_a.update(cx, |panel, _cx| {
- panel.width = Some(px(300.0));
- });
-
- panel_a.update_in(cx, |panel, window, cx| {
- panel.open_external_thread_with_server(
- Rc::new(StubAgentServer::default_response()),
- window,
- cx,
- );
- });
-
- cx.run_until_parked();
-
- panel_a.read_with(cx, |panel, cx| {
- assert!(
- panel.active_agent_thread(cx).is_some(),
- "workspace A should have an active thread after connection"
- );
- });
-
- let agent_type_a = panel_a.read_with(cx, |panel, _cx| panel.selected_agent.clone());
-
- // --- Set up workspace B: ClaudeCode, width=400, no active thread ---
- let panel_b = workspace_b.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_b.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
- });
-
- panel_b.update(cx, |panel, _cx| {
- panel.width = Some(px(400.0));
- panel.selected_agent = AgentType::ClaudeCode;
- });
-
- // --- Serialize both panels ---
- panel_a.update(cx, |panel, cx| panel.serialize(cx));
- panel_b.update(cx, |panel, cx| panel.serialize(cx));
- cx.run_until_parked();
-
- // --- Load fresh panels for each workspace and verify independent state ---
- let prompt_builder = Arc::new(prompt_store::PromptBuilder::new(None).unwrap());
-
- let async_cx = cx.update(|window, cx| window.to_async(cx));
- let loaded_a = AgentPanel::load(workspace_a.downgrade(), prompt_builder.clone(), async_cx)
- .await
- .expect("panel A load should succeed");
- cx.run_until_parked();
-
- let async_cx = cx.update(|window, cx| window.to_async(cx));
- let loaded_b = AgentPanel::load(workspace_b.downgrade(), prompt_builder.clone(), async_cx)
- .await
- .expect("panel B load should succeed");
- cx.run_until_parked();
-
- // Workspace A should restore its thread, width, and agent type
- loaded_a.read_with(cx, |panel, _cx| {
- assert_eq!(
- panel.width,
- Some(px(300.0)),
- "workspace A width should be restored"
- );
- assert_eq!(
- panel.selected_agent, agent_type_a,
- "workspace A agent type should be restored"
- );
- assert!(
- panel.active_thread_view().is_some(),
- "workspace A should have its active thread restored"
- );
- });
-
- // Workspace B should restore its own width and agent type, with no thread
- loaded_b.read_with(cx, |panel, _cx| {
- assert_eq!(
- panel.width,
- Some(px(400.0)),
- "workspace B width should be restored"
- );
- assert_eq!(
- panel.selected_agent,
- AgentType::ClaudeCode,
- "workspace B agent type should be restored"
- );
- assert!(
- panel.active_thread_view().is_none(),
- "workspace B should have no active thread"
- );
- });
- }
-}
diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs
index d2beb4b7199bf1536985bed49958b0643703bce7..ee035eb29188a7360c6d33df5334467f397532a4 100644
--- a/crates/agent_ui/src/agent_ui.rs
+++ b/crates/agent_ui/src/agent_ui.rs
@@ -49,7 +49,7 @@ use std::any::TypeId;
use workspace::Workspace;
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
-pub use crate::agent_panel::{AgentPanel, AgentPanelEvent, ConcreteAssistantPanelDelegate};
+pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
use crate::agent_registry_ui::AgentRegistryPage;
pub use crate::inline_assistant::InlineAssistant;
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
@@ -422,12 +422,6 @@ fn update_command_palette_filter(cx: &mut App) {
filter.hide_action_types(&[TypeId::of::()]);
}
}
-
- if agent_v2_enabled {
- filter.show_namespace("multi_workspace");
- } else {
- filter.hide_namespace("multi_workspace");
- }
});
}
diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs
index 2066a7ad886614373b200f4e45dd3bb0034f72a2..48c597f0431c480ade5810db99c36a890ec65093 100644
--- a/crates/agent_ui/src/inline_prompt_editor.rs
+++ b/crates/agent_ui/src/inline_prompt_editor.rs
@@ -417,13 +417,8 @@ impl PromptEditor {
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) {
if inline_assistant_model_supports_images(cx)
- && let Some(task) = paste_images_as_context(
- self.editor.clone(),
- self.mention_set.clone(),
- self.workspace.clone(),
- window,
- cx,
- )
+ && let Some(task) =
+ paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
{
task.detach();
}
@@ -443,7 +438,7 @@ impl PromptEditor {
self.mention_set
.update(cx, |mention_set, _cx| mention_set.remove_invalid(&snapshot));
- if let Some(workspace) = Workspace::for_window(window, cx) {
+ if let Some(workspace) = window.root::().flatten() {
workspace.update(cx, |workspace, cx| {
let is_via_ssh = workspace.project().read(cx).is_via_remote_server();
diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs
index 707e7b45343363b9db440998190e319df1da5b80..ee796323e28c64fb4162bbb05f6f6f9555a12d38 100644
--- a/crates/agent_ui/src/mention_set.rs
+++ b/crates/agent_ui/src/mention_set.rs
@@ -297,9 +297,8 @@ impl MentionSet {
self.mentions.insert(crease_id, (mention_uri, task.clone()));
// Notify the user if we failed to load the mentioned context
- let workspace = workspace.downgrade();
- cx.spawn(async move |this, mut cx| {
- let result = task.await.notify_workspace_async_err(workspace, &mut cx);
+ cx.spawn_in(window, async move |this, cx| {
+ let result = task.await.notify_async_err(cx);
drop(tx);
if result.is_none() {
this.update(cx, |this, cx| {
@@ -645,7 +644,6 @@ pub(crate) async fn insert_images_as_context(
images: Vec,
editor: Entity,
mention_set: Entity,
- workspace: WeakEntity,
cx: &mut gpui::AsyncWindowContext,
) {
if images.is_empty() {
@@ -720,11 +718,7 @@ pub(crate) async fn insert_images_as_context(
mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone())
});
- if task
- .await
- .notify_workspace_async_err(workspace.clone(), cx)
- .is_none()
- {
+ if task.await.notify_async_err(cx).is_none() {
editor.update(cx, |editor, cx| {
editor.edit([(start_anchor..end_anchor, "")], cx);
});
@@ -738,12 +732,11 @@ pub(crate) async fn insert_images_as_context(
pub(crate) fn paste_images_as_context(
editor: Entity,
mention_set: Entity,
- workspace: WeakEntity,
window: &mut Window,
cx: &mut App,
) -> Option> {
let clipboard = cx.read_from_clipboard()?;
- Some(window.spawn(cx, async move |mut cx| {
+ Some(window.spawn(cx, async move |cx| {
use itertools::Itertools;
let (mut images, paths) = clipboard
.into_entries()
@@ -790,7 +783,7 @@ pub(crate) fn paste_images_as_context(
})
.ok();
- insert_images_as_context(images, editor, mention_set, workspace, &mut cx).await;
+ insert_images_as_context(images, editor, mention_set, cx).await;
}))
}
diff --git a/crates/agent_ui/src/ui/agent_notification.rs b/crates/agent_ui/src/ui/agent_notification.rs
index 371523f129869786f13d1a220747f4d0d944d1e5..34ca0bb32a82aa23d1b954554ce2dfec436bfe1c 100644
--- a/crates/agent_ui/src/ui/agent_notification.rs
+++ b/crates/agent_ui/src/ui/agent_notification.rs
@@ -75,16 +75,6 @@ pub enum AgentNotificationEvent {
impl EventEmitter for AgentNotification {}
-impl AgentNotification {
- pub fn accept(&mut self, cx: &mut Context) {
- cx.emit(AgentNotificationEvent::Accepted);
- }
-
- pub fn dismiss(&mut self, cx: &mut Context) {
- cx.emit(AgentNotificationEvent::Dismissed);
- }
-}
-
impl Render for AgentNotification {
fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
@@ -184,14 +174,14 @@ impl Render for AgentNotification {
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width()
.on_click({
- cx.listener(move |this, _event, _, cx| {
- this.accept(cx);
+ cx.listener(move |_this, _event, _, cx| {
+ cx.emit(AgentNotificationEvent::Accepted);
})
}),
)
.child(Button::new("dismiss", "Dismiss").full_width().on_click({
- cx.listener(move |this, _event, _, cx| {
- this.dismiss(cx);
+ cx.listener(move |_, _event, _, cx| {
+ cx.emit(AgentNotificationEvent::Dismissed);
})
})),
)
diff --git a/crates/collab/tests/integration/channel_guest_tests.rs b/crates/collab/tests/integration/channel_guest_tests.rs
index 85d69914a832c65260014f5f5792eb664879f715..0d98af2a188ce18cfab5905e5b464c77101dfa00 100644
--- a/crates/collab/tests/integration/channel_guest_tests.rs
+++ b/crates/collab/tests/integration/channel_guest_tests.rs
@@ -34,11 +34,9 @@ async fn test_channel_guests(
cx_a.executor().run_until_parked();
// Client B joins channel A as a guest
- cx_b.update(|cx| {
- workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
- })
- .await
- .unwrap();
+ cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
+ .await
+ .unwrap();
// b should be following a in the shared project.
// B is a guest,
@@ -78,11 +76,9 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
.await;
let project_a = client_a.build_test_project(cx_a).await;
- cx_a.update(|cx| {
- workspace::join_channel(channel_id, client_a.app_state.clone(), None, None, cx)
- })
- .await
- .unwrap();
+ cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx))
+ .await
+ .unwrap();
// Client A shares a project in the channel
active_call_a
@@ -92,11 +88,9 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
cx_a.run_until_parked();
// Client B joins channel A as a guest
- cx_b.update(|cx| {
- workspace::join_channel(channel_id, client_b.app_state.clone(), None, None, cx)
- })
- .await
- .unwrap();
+ cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
+ .await
+ .unwrap();
cx_a.run_until_parked();
// client B opens 1.txt as a guest
diff --git a/crates/collab/tests/integration/editor_tests.rs b/crates/collab/tests/integration/editor_tests.rs
index a973c9f17ec5488746a9ad6594a3e99fb711c203..1612e32833dd07dd5fa2294d5bb5a90442883f71 100644
--- a/crates/collab/tests/integration/editor_tests.rs
+++ b/crates/collab/tests/integration/editor_tests.rs
@@ -19,8 +19,7 @@ use fs::Fs;
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
use git::repository::repo_path;
use gpui::{
- App, AppContext as _, Entity, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext,
- VisualTestContext,
+ App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,
};
use indoc::indoc;
use language::{FakeLspAdapter, language_settings::language_settings, rust_lang};
@@ -52,7 +51,7 @@ use std::{
};
use text::Point;
use util::{path, rel_path::rel_path, uri};
-use workspace::{CloseIntent, MultiWorkspace, Workspace};
+use workspace::{CloseIntent, Workspace};
#[gpui::test(iterations = 10)]
async fn test_host_disconnect(
@@ -96,46 +95,34 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
- let window_b = cx_b.add_window(|window, cx| {
- let workspace = cx.new(|cx| {
- Workspace::new(
- None,
- project_b.clone(),
- client_b.app_state.clone(),
- window,
- cx,
- )
- });
- MultiWorkspace::new(workspace, cx)
+ let workspace_b = cx_b.add_window(|window, cx| {
+ Workspace::new(
+ None,
+ project_b.clone(),
+ client_b.app_state.clone(),
+ window,
+ cx,
+ )
});
- let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b);
- let workspace_b = window_b
- .root(cx_b)
- .unwrap()
- .read_with(cx_b, |multi_workspace, _| {
- multi_workspace.workspace().clone()
- });
+ let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
+ let workspace_b_view = workspace_b.root(cx_b).unwrap();
- let editor_b: Entity = workspace_b
- .update_in(cx_b, |workspace, window, cx| {
+ let editor_b = workspace_b
+ .update(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx)
})
+ .unwrap()
.await
.unwrap()
.downcast::()
.unwrap();
//TODO: focus
- assert!(
- cx_b.update_window_entity(&editor_b, |editor: &mut Editor, window, _| editor
- .is_focused(window))
- );
- editor_b.update_in(cx_b, |editor: &mut Editor, window, cx| {
- editor.insert("X", window, cx)
- });
+ assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));
+ editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
cx_b.update(|_, cx| {
- assert!(workspace_b.read(cx).is_edited());
+ assert!(workspace_b_view.read(cx).is_edited());
});
// Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
@@ -153,16 +140,19 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
// Ensure client B's edited state is reset and that the whole window is blurred.
- workspace_b.update(cx_b, |workspace, cx| {
- assert!(workspace.active_modal::(cx).is_some());
- assert!(!workspace.is_edited());
- });
+ workspace_b
+ .update(cx_b, |workspace, _, cx| {
+ assert!(workspace.active_modal::(cx).is_some());
+ assert!(!workspace.is_edited());
+ })
+ .unwrap();
// Ensure client B is not prompted to save edits when closing window after disconnecting.
- let can_close: bool = workspace_b
- .update_in(cx_b, |workspace, window, cx| {
+ let can_close = workspace_b
+ .update(cx_b, |workspace, window, cx| {
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
})
+ .unwrap()
.await
.unwrap();
assert!(can_close);
diff --git a/crates/collab/tests/integration/following_tests.rs b/crates/collab/tests/integration/following_tests.rs
index 6bdb06a6c5a0ffb95bc75a026a26c4797030f8ce..295105ecbd9f8663469276fe4d0d197708a4254e 100644
--- a/crates/collab/tests/integration/following_tests.rs
+++ b/crates/collab/tests/integration/following_tests.rs
@@ -17,7 +17,7 @@ use serde_json::json;
use settings::SettingsStore;
use text::{Point, ToPoint};
use util::{path, rel_path::rel_path, test::sample_text};
-use workspace::{CollaboratorId, MultiWorkspace, SplitDirection, Workspace, item::ItemHandle as _};
+use workspace::{CollaboratorId, SplitDirection, Workspace, item::ItemHandle as _};
use super::TestClient;
@@ -1555,9 +1555,9 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
let mut cx_b2 = VisualTestContext::from_window(window_b_project_a, cx_b);
let workspace_b_project_a = window_b_project_a
- .downcast::()
+ .downcast::()
.unwrap()
- .read_with(cx_b, |mw, _| mw.workspace().clone())
+ .root(cx_b)
.unwrap();
// assert that b is following a in project a in w.rs
@@ -1657,9 +1657,9 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
.unwrap();
let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b, cx_a);
let workspace_a_project_b = window_a_project_b
- .downcast::()
+ .downcast::()
.unwrap()
- .read_with(cx_a, |mw, _| mw.workspace().clone())
+ .root(cx_a)
.unwrap();
executor.run_until_parked();
@@ -2144,7 +2144,7 @@ pub(crate) async fn join_channel(
client: &TestClient,
cx: &mut TestAppContext,
) -> anyhow::Result<()> {
- cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, None, cx))
+ cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx))
.await
}
diff --git a/crates/collab/tests/integration/git_tests.rs b/crates/collab/tests/integration/git_tests.rs
index 63cee5886d5096cb0e3fbee3886b90f66c675bfa..1378fcf95c63c883ee8dd424dc10ac67ccd774bd 100644
--- a/crates/collab/tests/integration/git_tests.rs
+++ b/crates/collab/tests/integration/git_tests.rs
@@ -3,11 +3,11 @@ use std::path::Path;
use call::ActiveCall;
use git::status::{FileStatus, StatusCode, TrackedStatus};
use git_ui::project_diff::ProjectDiff;
-use gpui::{AppContext as _, TestAppContext, VisualTestContext};
+use gpui::{TestAppContext, VisualTestContext};
use project::ProjectPath;
use serde_json::json;
use util::{path, rel_path::rel_path};
-use workspace::{MultiWorkspace, Workspace};
+use workspace::Workspace;
//
use crate::TestServer;
@@ -57,25 +57,17 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
cx_b.update(editor::init);
cx_b.update(git_ui::init);
let project_b = client_b.join_remote_project(project_id, cx_b).await;
- let window_b = cx_b.add_window(|window, cx| {
- let workspace = cx.new(|cx| {
- Workspace::new(
- None,
- project_b.clone(),
- client_b.app_state.clone(),
- window,
- cx,
- )
- });
- MultiWorkspace::new(workspace, cx)
+ let workspace_b = cx_b.add_window(|window, cx| {
+ Workspace::new(
+ None,
+ project_b.clone(),
+ client_b.app_state.clone(),
+ window,
+ cx,
+ )
});
- let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b);
- let workspace_b = window_b
- .root(cx_b)
- .unwrap()
- .read_with(cx_b, |multi_workspace, _| {
- multi_workspace.workspace().clone()
- });
+ let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
+ let workspace_b = workspace_b.root(cx_b).unwrap();
cx_b.update(|window, cx| {
window
diff --git a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs
index c6daedff803b6f5cada32750f90dd1adca5aeda6..1f4dd0d353234f61675b5beefd2226c3d684c062 100644
--- a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs
+++ b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs
@@ -8,9 +8,7 @@ use editor::{Editor, EditorMode, MultiBuffer};
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _, RemoveOptions};
use futures::StreamExt as _;
-use gpui::{
- AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as _, VisualContext as _,
-};
+use gpui::{AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as _, VisualContext};
use http_client::BlockedHttpClient;
use language::{
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
@@ -665,7 +663,7 @@ async fn test_remote_server_debugger(
let workspace_window = cx_a
.window_handle()
- .downcast::()
+ .downcast::()
.unwrap();
let session = debugger_ui::tests::start_debug_session(&workspace_window, cx_a, |_| {}).unwrap();
@@ -673,16 +671,13 @@ async fn test_remote_server_debugger(
debug_panel.update(cx_a, |debug_panel, cx| {
assert_eq!(
debug_panel.active_session().unwrap().read(cx).session(cx),
- session.clone()
+ session
)
});
- session.update(
- cx_a,
- |session: &mut project::debugger::session::Session, _| {
- assert_eq!(session.binary().unwrap().command.as_deref(), Some("mock"));
- },
- );
+ session.update(cx_a, |session, _| {
+ assert_eq!(session.binary().unwrap().command.as_deref(), Some("mock"));
+ });
let shutdown_session = workspace.update(cx_a, |workspace, cx| {
workspace.project().update(cx, |project, cx| {
@@ -777,7 +772,7 @@ async fn test_slow_adapter_startup_retries(
let workspace_window = cx_a
.window_handle()
- .downcast::()
+ .downcast::()
.unwrap();
let count = Arc::new(AtomicUsize::new(0));
@@ -809,10 +804,7 @@ async fn test_slow_adapter_startup_retries(
.unwrap();
cx_a.run_until_parked();
- let client = session.update(
- cx_a,
- |session: &mut project::debugger::session::Session, _| session.adapter_client().unwrap(),
- );
+ let client = session.update(cx_a, |session, _| session.adapter_client().unwrap());
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
diff --git a/crates/collab/tests/integration/test_server.rs b/crates/collab/tests/integration/test_server.rs
index 0a2ec25cde361259344493d3532afeb2050aea71..f28d247f67a149ef6d489b9bc6ab7b43eb77350f 100644
--- a/crates/collab/tests/integration/test_server.rs
+++ b/crates/collab/tests/integration/test_server.rs
@@ -45,7 +45,7 @@ use std::{
},
};
use util::path;
-use workspace::{MultiWorkspace, Workspace, WorkspaceStore};
+use workspace::{Workspace, WorkspaceStore};
use livekit_client::test::TestServer as LivekitTestServer;
@@ -843,7 +843,7 @@ impl TestClient {
channel_id: ChannelId,
cx: &'a mut TestAppContext,
) -> (Entity, &'a mut VisualTestContext) {
- cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, None, cx))
+ cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
.await
.unwrap();
cx.run_until_parked();
@@ -897,19 +897,10 @@ impl TestClient {
project: &Entity,
cx: &'a mut TestAppContext,
) -> (Entity, &'a mut VisualTestContext) {
- let app_state = self.app_state.clone();
- let project = project.clone();
- let window = cx.add_window(|window, cx| {
+ cx.add_window_view(|window, cx| {
window.activate_window();
- let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
- MultiWorkspace::new(workspace, cx)
- });
- let cx = VisualTestContext::from_window(*window, cx).into_mut();
- cx.run_until_parked();
- let workspace = window
- .read_with(cx, |mw, _| mw.workspace().clone())
- .unwrap();
- (workspace, cx)
+ Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
+ })
}
pub async fn build_test_workspace<'a>(
@@ -917,33 +908,19 @@ impl TestClient {
cx: &'a mut TestAppContext,
) -> (Entity, &'a mut VisualTestContext) {
let project = self.build_test_project(cx).await;
- let app_state = self.app_state.clone();
- let window = cx.add_window(|window, cx| {
+ cx.add_window_view(|window, cx| {
window.activate_window();
- let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
- MultiWorkspace::new(workspace, cx)
- });
- let cx = VisualTestContext::from_window(*window, cx).into_mut();
- let workspace = window
- .read_with(cx, |mw, _| mw.workspace().clone())
- .unwrap();
- (workspace, cx)
+ Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
+ })
}
pub fn active_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (Entity, &'a mut VisualTestContext) {
- let window = cx.update(|cx| {
- cx.active_window()
- .unwrap()
- .downcast::()
- .unwrap()
- });
+ let window = cx.update(|cx| cx.active_window().unwrap().downcast::().unwrap());
- let entity = window
- .read_with(cx, |mw, _| mw.workspace().clone())
- .unwrap();
+ let entity = window.root(cx).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), cx).into_mut();
// it might be nice to try and cleanup these at the end of each test.
(entity, cx)
@@ -954,15 +931,8 @@ pub fn open_channel_notes(
channel_id: ChannelId,
cx: &mut VisualTestContext,
) -> Task>> {
- let window = cx.update(|_, cx| {
- cx.active_window()
- .unwrap()
- .downcast::()
- .unwrap()
- });
- let entity = window
- .read_with(cx, |mw, _| mw.workspace().clone())
- .unwrap();
+ let window = cx.update(|_, cx| cx.active_window().unwrap().downcast::().unwrap());
+ let entity = window.root(cx).unwrap();
cx.update(|window, cx| ChannelView::open(channel_id, None, entity.clone(), window, cx))
}
diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs
index c0a68efdc7107800a0abfd5c522e5b0ed541a964..663d64d56d3e9832a6a92c2916fa62d22afd23e6 100644
--- a/crates/collab_ui/src/collab_panel.rs
+++ b/crates/collab_ui/src/collab_panel.rs
@@ -36,8 +36,7 @@ use ui::{
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::{
- CopyRoomId, Deafen, LeaveCall, MultiWorkspace, Mute, OpenChannelNotes, ScreenShare,
- ShareProject, Workspace,
+ CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotifyResultExt},
};
@@ -121,7 +120,6 @@ pub fn init(cx: &mut App) {
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
let romo_id_fut = room.read(cx).room_id();
- let workspace_handle = cx.weak_entity();
cx.spawn(async move |workspace, cx| {
let room_id = romo_id_fut.await.context("Failed to get livekit room")?;
workspace.update(cx, |workspace, cx| {
@@ -136,7 +134,7 @@ pub fn init(cx: &mut App) {
);
})
})
- .detach_and_notify_err(workspace_handle, window, cx);
+ .detach_and_notify_err(window, cx);
} else {
workspace.show_error(&"There’s no active call; join one first.", cx);
}
@@ -2179,13 +2177,12 @@ impl CollabPanel {
&["Remove", "Cancel"],
cx,
);
- let workspace = self.workspace.clone();
- cx.spawn_in(window, async move |this, mut cx| {
+ cx.spawn_in(window, async move |this, cx| {
if answer.await? == 0 {
channel_store
.update(cx, |channels, _| channels.remove_channel(channel_id))
.await
- .notify_workspace_async_err(workspace, &mut cx);
+ .notify_async_err(cx);
this.update_in(cx, |_, window, cx| cx.focus_self(window))
.ok();
}
@@ -2214,13 +2211,12 @@ impl CollabPanel {
&["Remove", "Cancel"],
cx,
);
- let workspace = self.workspace.clone();
- cx.spawn_in(window, async move |_, mut cx| {
+ cx.spawn_in(window, async move |_, cx| {
if answer.await? == 0 {
user_store
.update(cx, |store, cx| store.remove_contact(user_id, cx))
.await
- .notify_workspace_async_err(workspace, &mut cx);
+ .notify_async_err(cx);
}
anyhow::Ok(())
})
@@ -2271,15 +2267,13 @@ impl CollabPanel {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
-
- let Some(handle) = window.window_handle().downcast::() else {
+ let Some(handle) = window.window_handle().downcast::() else {
return;
};
workspace::join_channel(
channel_id,
workspace.read(cx).app_state().clone(),
Some(handle),
- Some(self.workspace.clone()),
cx,
)
.detach_and_prompt_err("Failed to join channel", window, cx, |_, _, _| None)
@@ -2322,13 +2316,12 @@ impl CollabPanel {
.full_width()
.on_click(cx.listener(|this, _, window, cx| {
let client = this.client.clone();
- let workspace = this.workspace.clone();
- cx.spawn_in(window, async move |_, mut cx| {
+ cx.spawn_in(window, async move |_, cx| {
client
- .connect(true, &mut cx)
+ .connect(true, cx)
.await
.into_response()
- .notify_workspace_async_err(workspace, &mut cx);
+ .notify_async_err(cx);
})
.detach()
})),
diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs
index 24b1218305474a29ac2d2e7c8e0a212d6d757522..dd48f95e0af6daeaf2a0a15b7b9595cb4c08aba2 100644
--- a/crates/copilot_ui/src/sign_in.rs
+++ b/crates/copilot_ui/src/sign_in.rs
@@ -35,7 +35,7 @@ pub fn initiate_sign_out(copilot: Entity, window: &mut Window, cx: &mut
cx.update(|window, cx| copilot_toast(Some("Signed out of Copilot"), window, cx))
}
Err(err) => cx.update(|window, cx| {
- if let Some(workspace) = Workspace::for_window(window, cx) {
+ if let Some(workspace) = window.root::().flatten() {
workspace.update(cx, |workspace, cx| {
workspace.show_error(&err, cx);
})
@@ -82,7 +82,7 @@ fn open_copilot_code_verification_window(copilot: &Entity, window: &Win
fn copilot_toast(message: Option<&'static str>, window: &Window, cx: &mut App) {
const NOTIFICATION_ID: NotificationId = NotificationId::unique::();
- let Some(workspace) = Workspace::for_window(window, cx) else {
+ let Some(workspace) = window.root::().flatten() else {
return;
};
diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs
index 438adcdf44921aa1d2590694608c139e9174d788..8ea877b35bfaf57bb258e7e179fa5b71f2b518ea 100644
--- a/crates/db/src/kvp.rs
+++ b/crates/db/src/kvp.rs
@@ -1,4 +1,3 @@
-use anyhow::Context as _;
use gpui::App;
use sqlez_macros::sql;
use util::ResultExt as _;
@@ -14,22 +13,12 @@ pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnect
impl Domain for KeyValueStore {
const NAME: &str = stringify!(KeyValueStore);
- const MIGRATIONS: &[&str] = &[
- sql!(
- CREATE TABLE IF NOT EXISTS kv_store(
- key TEXT PRIMARY KEY,
- value TEXT NOT NULL
- ) STRICT;
- ),
- sql!(
- CREATE TABLE IF NOT EXISTS scoped_kv_store(
- namespace TEXT NOT NULL,
- key TEXT NOT NULL,
- value TEXT NOT NULL,
- PRIMARY KEY(namespace, key)
- ) STRICT;
- ),
- ];
+ const MIGRATIONS: &[&str] = &[sql!(
+ CREATE TABLE IF NOT EXISTS kv_store(
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+ ) STRICT;
+ )];
}
crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
@@ -80,64 +69,6 @@ impl KeyValueStore {
DELETE FROM kv_store WHERE key = (?)
}
}
-
- pub fn scoped<'a>(&'a self, namespace: &'a str) -> ScopedKeyValueStore<'a> {
- ScopedKeyValueStore {
- store: self,
- namespace,
- }
- }
-}
-
-pub struct ScopedKeyValueStore<'a> {
- store: &'a KeyValueStore,
- namespace: &'a str,
-}
-
-impl ScopedKeyValueStore<'_> {
- pub fn read(&self, key: &str) -> anyhow::Result