Detailed changes
@@ -83,8 +83,9 @@ use ui::{
};
use util::{ResultExt as _, debug_panic};
use workspace::{
- CollaboratorId, DraggedSelection, DraggedTab, OpenResult, PathList, SerializedPathList,
- ToggleWorkspaceSidebar, ToggleZoom, ToolbarItemView, Workspace, WorkspaceId,
+ CollaboratorId, DraggedSelection, DraggedTab, OpenMode, OpenResult, PathList,
+ SerializedPathList, ToggleWorkspaceSidebar, ToggleZoom, ToolbarItemView, Workspace,
+ WorkspaceId,
dock::{DockPosition, Panel, PanelEvent},
};
use zed_actions::{
@@ -2939,7 +2940,15 @@ impl AgentPanel {
..
} = cx
.update(|_window, cx| {
- Workspace::new_local(all_paths, app_state, window_handle, None, None, false, cx)
+ Workspace::new_local(
+ all_paths,
+ app_state,
+ window_handle,
+ None,
+ None,
+ OpenMode::Add,
+ cx,
+ )
})?
.await?;
@@ -3062,8 +3071,8 @@ impl AgentPanel {
});
})?;
- new_window_handle.update(cx, |multi_workspace, _window, cx| {
- multi_workspace.activate(new_workspace.clone(), cx);
+ new_window_handle.update(cx, |multi_workspace, window, cx| {
+ multi_workspace.activate(new_workspace.clone(), window, cx);
})?;
this.update_in(cx, |this, window, cx| {
@@ -2413,7 +2413,7 @@ impl ConversationView {
.update(cx, |multi_workspace, window, cx| {
window.activate_window();
if let Some(workspace) = workspace_handle.upgrade() {
- multi_workspace.activate(workspace.clone(), cx);
+ multi_workspace.activate(workspace.clone(), window, cx);
workspace.update(cx, |workspace, cx| {
workspace.focus_panel::<AgentPanel>(window, cx);
});
@@ -20,7 +20,9 @@ use settings::Settings;
use std::{path::PathBuf, sync::Arc};
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use util::{ResultExt, debug_panic};
-use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
+use workspace::{
+ ModalView, MultiWorkspace, OpenMode, Workspace, notifications::DetachAndPromptErr,
+};
use crate::git_panel::show_error_toast;
@@ -354,7 +356,7 @@ impl WorktreeListDelegate {
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_workspace_for_paths(
- replace_current_window,
+ OpenMode::Replace,
vec![new_worktree_path],
window,
cx,
@@ -407,10 +409,15 @@ impl WorktreeListDelegate {
else {
return;
};
+ let open_mode = if replace_current_window {
+ OpenMode::Replace
+ } else {
+ OpenMode::NewWindow
+ };
if is_local {
let open_task = workspace.update(cx, |workspace, cx| {
- workspace.open_workspace_for_paths(replace_current_window, vec![path], window, cx)
+ workspace.open_workspace_for_paths(open_mode, vec![path], window, cx)
});
cx.spawn(async move |_, _| {
open_task?.await?;
@@ -951,16 +958,6 @@ impl PickerDelegate for WorktreeListDelegate {
})
.child(
Button::new("open-in-new-window", "Open in New Window")
- .key_binding(
- KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(|_, window, cx| {
- window.dispatch_action(menu::Confirm.boxed_clone(), cx)
- }),
- )
- .child(
- Button::new("open-in-window", "Open")
.key_binding(
KeyBinding::for_action_in(
&menu::SecondaryConfirm,
@@ -973,6 +970,16 @@ impl PickerDelegate for WorktreeListDelegate {
window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
}),
)
+ .child(
+ Button::new("open-in-window", "Open")
+ .key_binding(
+ KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
+ .map(|kb| kb.size(rems_from_px(12.))),
+ )
+ .on_click(|_, window, cx| {
+ window.dispatch_action(menu::Confirm.boxed_clone(), cx)
+ }),
+ )
.into_any(),
)
}
@@ -71,8 +71,8 @@ use util::{
rel_path::{RelPath, RelPathBuf},
};
use workspace::{
- DraggedSelection, OpenInTerminal, OpenOptions, OpenVisible, PreviewTabsSettings, SelectedEntry,
- SplitDirection, Workspace,
+ DraggedSelection, OpenInTerminal, OpenMode, OpenOptions, OpenVisible, PreviewTabsSettings,
+ SelectedEntry, SplitDirection, Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
};
@@ -7126,7 +7126,7 @@ impl Render for ProjectPanel {
.workspace
.update(cx, |workspace, cx| {
workspace.open_workspace_for_paths(
- true,
+ OpenMode::Replace,
external_paths.paths().to_owned(),
window,
cx,
@@ -125,7 +125,7 @@ impl DisconnectedOverlay {
paths,
app_state,
OpenOptions {
- replace_window: Some(window_handle),
+ requesting_window: Some(window_handle),
..Default::default()
},
cx,
@@ -46,7 +46,7 @@ use ui::{
};
use util::{ResultExt, paths::PathExt};
use workspace::{
- HistoryManager, ModalView, MultiWorkspace, OpenOptions, OpenVisible, PathList,
+ HistoryManager, ModalView, MultiWorkspace, OpenMode, OpenOptions, OpenVisible, PathList,
SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId,
notifications::DetachAndPromptErr, with_active_or_new_workspace,
};
@@ -262,13 +262,13 @@ pub fn init(cx: &mut App) {
user: None,
});
- let replace_window = match create_new_window {
+ let requesting_window = match create_new_window {
false => window_handle,
true => None,
};
let open_options = workspace::OpenOptions {
- replace_window,
+ requesting_window,
..Default::default()
};
@@ -321,7 +321,7 @@ pub fn init(cx: &mut App) {
let fs = workspace.project().read(cx).fs().clone();
add_wsl_distro(fs, &open_wsl.distro, cx);
let open_options = OpenOptions {
- replace_window: window.window_handle().downcast::<MultiWorkspace>(),
+ requesting_window: window.window_handle().downcast::<MultiWorkspace>(),
..Default::default()
};
@@ -1031,14 +1031,14 @@ impl PickerDelegate for RecentProjectsDelegate {
if let Some(handle) = window.window_handle().downcast::<MultiWorkspace>() {
cx.defer(move |cx| {
handle
- .update(cx, |multi_workspace, _window, cx| {
+ .update(cx, |multi_workspace, window, cx| {
let workspace = multi_workspace
.workspaces()
.iter()
.find(|ws| ws.read(cx).database_id() == Some(workspace_id))
.cloned();
if let Some(workspace) = workspace {
- multi_workspace.activate(workspace, cx);
+ multi_workspace.activate(workspace, window, cx);
}
})
.log_err();
@@ -1079,7 +1079,12 @@ impl PickerDelegate for RecentProjectsDelegate {
cx.defer(move |cx| {
if let Some(task) = handle
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.open_project(paths, window, cx)
+ multi_workspace.open_project(
+ paths,
+ OpenMode::Replace,
+ window,
+ cx,
+ )
})
.log_err()
{
@@ -1090,7 +1095,12 @@ impl PickerDelegate for RecentProjectsDelegate {
return;
} else {
workspace
- .open_workspace_for_paths(false, paths, window, cx)
+ .open_workspace_for_paths(
+ OpenMode::NewWindow,
+ paths,
+ window,
+ cx,
+ )
.detach_and_prompt_err(
"Failed to open project",
window,
@@ -1107,7 +1117,7 @@ impl PickerDelegate for RecentProjectsDelegate {
None
};
let open_options = OpenOptions {
- replace_window,
+ requesting_window: replace_window,
..Default::default()
};
if let RemoteConnectionOptions::Ssh(connection) = &mut connection {
@@ -1804,12 +1814,13 @@ impl RecentProjectsDelegate {
cx.defer(move |cx| {
handle
.update(cx, |multi_workspace, window, cx| {
- let index = multi_workspace
+ let workspace = multi_workspace
.workspaces()
.iter()
- .position(|ws| ws.read(cx).database_id() == Some(workspace_id));
- if let Some(index) = index {
- multi_workspace.remove_workspace(index, window, cx);
+ .find(|ws| ws.read(cx).database_id() == Some(workspace_id))
+ .cloned();
+ if let Some(workspace) = workspace {
+ multi_workspace.remove(&workspace, window, cx);
}
})
.log_err();
@@ -1886,7 +1897,7 @@ mod tests {
use super::*;
#[gpui::test]
- async fn test_dirty_workspace_survives_when_opening_recent_project(cx: &mut TestAppContext) {
+ async fn test_dirty_workspace_replaced_when_opening_recent_project(cx: &mut TestAppContext) {
let app_state = init_test(cx);
cx.update(|cx| {
@@ -1995,6 +2006,15 @@ mod tests {
cx.dispatch_action(*multi_workspace, menu::Confirm);
cx.run_until_parked();
+ // prepare_to_close triggers a save prompt for the dirty buffer.
+ // Choose "Don't Save" (index 2) to discard and continue replacing.
+ assert!(
+ cx.has_pending_prompt(),
+ "Should prompt to save dirty buffer before replacing workspace"
+ );
+ cx.simulate_prompt_answer("Don't Save");
+ cx.run_until_parked();
+
multi_workspace
.update(cx, |multi_workspace, _, cx| {
assert!(
@@ -2007,26 +2027,16 @@ mod tests {
);
assert!(
- multi_workspace.workspaces().len() >= 2,
- "Should have at least 2 workspaces: the dirty one and the newly opened one"
+ !multi_workspace.workspaces().contains(&dirty_workspace),
+ "The original dirty workspace should have been replaced"
);
assert!(
- multi_workspace.workspaces().contains(&dirty_workspace),
- "The original dirty workspace should still be present"
- );
-
- assert!(
- dirty_workspace.read(cx).is_edited(),
- "The original workspace should still be dirty"
+ !multi_workspace.workspace().read(cx).is_edited(),
+ "The active workspace should be the freshly opened one, not dirty"
);
})
.unwrap();
-
- assert!(
- !cx.has_pending_prompt(),
- "No save prompt in multi-workspace mode — dirty workspace survives in background"
- );
}
fn open_recent_projects(
@@ -132,7 +132,7 @@ pub async fn open_remote_project(
open_options: workspace::OpenOptions,
cx: &mut AsyncApp,
) -> Result<()> {
- let created_new_window = open_options.replace_window.is_none();
+ let created_new_window = open_options.requesting_window.is_none();
let (existing, open_visible) = find_existing_workspace(
&paths,
@@ -159,7 +159,7 @@ pub async fn open_remote_project(
let open_results = existing_window
.update(cx, |multi_workspace, window, cx| {
window.activate_window();
- multi_workspace.activate(existing_workspace.clone(), cx);
+ multi_workspace.activate(existing_workspace.clone(), window, cx);
existing_workspace.update(cx, |workspace, cx| {
workspace.open_paths(
resolved_paths,
@@ -201,7 +201,7 @@ pub async fn open_remote_project(
);
}
- let (window, initial_workspace) = if let Some(window) = open_options.replace_window {
+ let (window, initial_workspace) = if let Some(window) = open_options.requesting_window {
let workspace = window.update(cx, |multi_workspace, _, _| {
multi_workspace.workspace().clone()
})?;
@@ -854,7 +854,7 @@ mod tests {
paths,
app_state,
workspace::OpenOptions {
- replace_window: Some(window),
+ requesting_window: Some(window),
..Default::default()
},
&mut async_cx,
@@ -1566,7 +1566,7 @@ impl RemoteServerProjects {
project.paths.into_iter().map(PathBuf::from).collect(),
app_state,
OpenOptions {
- replace_window,
+ requesting_window: replace_window,
..OpenOptions::default()
},
cx,
@@ -1894,7 +1894,7 @@ impl RemoteServerProjects {
vec![starting_dir].into_iter().map(PathBuf::from).collect(),
app_state,
OpenOptions {
- replace_window,
+ requesting_window: replace_window,
..OpenOptions::default()
},
cx,
@@ -17,8 +17,8 @@ use ui::{KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use ui_input::ErasedEditor;
use util::{ResultExt, paths::PathExt};
use workspace::{
- MultiWorkspace, OpenOptions, PathList, SerializedWorkspaceLocation, Workspace, WorkspaceDb,
- WorkspaceId, notifications::DetachAndPromptErr,
+ MultiWorkspace, OpenMode, OpenOptions, PathList, SerializedWorkspaceLocation, Workspace,
+ WorkspaceDb, WorkspaceId, notifications::DetachAndPromptErr,
};
use crate::{highlights_for_path, icon_for_remote_connection, open_remote_project};
@@ -272,7 +272,7 @@ impl PickerDelegate for SidebarRecentProjectsDelegate {
cx.defer(move |cx| {
if let Some(task) = handle
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.open_project(paths, window, cx)
+ multi_workspace.open_project(paths, OpenMode::Activate, window, cx)
})
.log_err()
{
@@ -287,7 +287,7 @@ impl PickerDelegate for SidebarRecentProjectsDelegate {
let app_state = workspace.app_state().clone();
let replace_window = window.window_handle().downcast::<MultiWorkspace>();
let open_options = OpenOptions {
- replace_window,
+ requesting_window: replace_window,
..Default::default()
};
if let RemoteConnectionOptions::Ssh(connection) = &mut connection {
@@ -245,14 +245,16 @@ impl WslOpenModal {
true => secondary,
false => !secondary,
};
- let replace_window = match replace_current_window {
- true => window.window_handle().downcast::<MultiWorkspace>(),
- false => None,
+ let open_mode = if replace_current_window {
+ workspace::OpenMode::Replace
+ } else {
+ workspace::OpenMode::NewWindow
};
let paths = self.paths.clone();
let open_options = workspace::OpenOptions {
- replace_window,
+ requesting_window: window.window_handle().downcast::<MultiWorkspace>(),
+ open_mode,
..Default::default()
};
@@ -1334,8 +1334,11 @@ impl Sidebar {
this.focused_thread = None;
if let Some(multi_workspace) = this.multi_workspace.upgrade() {
multi_workspace.update(cx, |multi_workspace, cx| {
- multi_workspace
- .activate(workspace_for_open.clone(), cx);
+ multi_workspace.activate(
+ workspace_for_open.clone(),
+ window,
+ cx,
+ );
});
}
if AgentPanel::is_visible(&workspace_for_open, cx) {
@@ -1438,13 +1441,7 @@ impl Sidebar {
if let Some(mw) = multi_workspace_for_worktree.upgrade() {
let ws = workspace_for_remove_worktree.clone();
mw.update(cx, |multi_workspace, cx| {
- if let Some(index) = multi_workspace
- .workspaces()
- .iter()
- .position(|w| *w == ws)
- {
- multi_workspace.remove_workspace(index, window, cx);
- }
+ multi_workspace.remove(&ws, window, cx);
});
}
} else {
@@ -1474,7 +1471,7 @@ impl Sidebar {
move |window, cx| {
if let Some(mw) = multi_workspace_for_add.upgrade() {
mw.update(cx, |mw, cx| {
- mw.activate(workspace_for_add.clone(), cx);
+ mw.activate(workspace_for_add.clone(), window, cx);
});
}
workspace_for_add.update(cx, |workspace, cx| {
@@ -1497,14 +1494,11 @@ impl Sidebar {
move |window, cx| {
if let Some(mw) = multi_workspace_for_move.upgrade() {
mw.update(cx, |multi_workspace, cx| {
- if let Some(index) = multi_workspace
- .workspaces()
- .iter()
- .position(|w| *w == workspace_for_move)
- {
- multi_workspace
- .move_workspace_to_new_window(index, window, cx);
- }
+ multi_workspace.move_workspace_to_new_window(
+ &workspace_for_move,
+ window,
+ cx,
+ );
});
}
},
@@ -1520,11 +1514,7 @@ impl Sidebar {
if let Some(mw) = multi_workspace_for_remove.upgrade() {
let ws = workspace_for_remove.clone();
mw.update(cx, |multi_workspace, cx| {
- if let Some(index) =
- multi_workspace.workspaces().iter().position(|w| *w == ws)
- {
- multi_workspace.remove_workspace(index, window, cx);
- }
+ multi_workspace.remove(&ws, window, cx);
});
}
})
@@ -1942,7 +1932,7 @@ impl Sidebar {
self.record_thread_access(&metadata.session_id);
multi_workspace.update(cx, |multi_workspace, cx| {
- multi_workspace.activate(workspace.clone(), cx);
+ multi_workspace.activate(workspace.clone(), window, cx);
});
Self::load_agent_thread_in_workspace(workspace, metadata, true, window, cx);
@@ -1962,7 +1952,7 @@ impl Sidebar {
let activated = target_window
.update(cx, |multi_workspace, window, cx| {
window.activate_window();
- multi_workspace.activate(workspace.clone(), cx);
+ multi_workspace.activate(workspace.clone(), window, cx);
Self::load_agent_thread_in_workspace(&workspace, &metadata, true, window, cx);
})
.log_err()
@@ -2024,7 +2014,9 @@ impl Sidebar {
let paths: Vec<std::path::PathBuf> =
path_list.paths().iter().map(|p| p.to_path_buf()).collect();
- let open_task = multi_workspace.update(cx, |mw, cx| mw.open_project(paths, window, cx));
+ let open_task = multi_workspace.update(cx, |mw, cx| {
+ mw.open_project(paths, workspace::OpenMode::Activate, window, cx)
+ });
cx.spawn_in(window, async move |this, cx| {
let workspace = open_task.await?;
@@ -2512,7 +2504,7 @@ impl Sidebar {
} => {
if let Some(mw) = weak_multi_workspace.upgrade() {
mw.update(cx, |mw, cx| {
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
}
this.focused_thread = Some(metadata.session_id.clone());
@@ -2527,7 +2519,7 @@ impl Sidebar {
} => {
if let Some(mw) = weak_multi_workspace.upgrade() {
mw.update(cx, |mw, cx| {
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
}
this.record_thread_access(&metadata.session_id);
@@ -2543,7 +2535,7 @@ impl Sidebar {
if let Some(mw) = weak_multi_workspace.upgrade() {
if let Some(original_ws) = &original_workspace {
mw.update(cx, |mw, cx| {
- mw.activate(original_ws.clone(), cx);
+ mw.activate(original_ws.clone(), window, cx);
});
}
}
@@ -2594,7 +2586,7 @@ impl Sidebar {
if let Some((metadata, workspace)) = initial_preview {
if let Some(mw) = self.multi_workspace.upgrade() {
mw.update(cx, |mw, cx| {
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
}
self.focused_thread = Some(metadata.session_id.clone());
@@ -2895,7 +2887,7 @@ impl Sidebar {
self.focused_thread = None;
multi_workspace.update(cx, |multi_workspace, cx| {
- multi_workspace.activate(workspace.clone(), cx);
+ multi_workspace.activate(workspace.clone(), window, cx);
});
workspace.update(cx, |workspace, cx| {
@@ -385,7 +385,8 @@ async fn test_workspace_lifecycle(cx: &mut TestAppContext) {
// Remove the second workspace
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.remove_workspace(1, window, cx);
+ let workspace = mw.workspaces()[1].clone();
+ mw.remove(&workspace, window, cx);
});
cx.run_until_parked();
@@ -1737,7 +1738,8 @@ async fn test_confirm_on_historical_thread_activates_workspace(cx: &mut TestAppC
// Switch to workspace 1 so we can verify the confirm switches back.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(1, window, cx);
+ let workspace = mw.workspaces()[1].clone();
+ mw.activate(workspace, window, cx);
});
cx.run_until_parked();
assert_eq!(
@@ -2001,7 +2003,8 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) {
});
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
cx.run_until_parked();
@@ -2056,7 +2059,8 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) {
// a workspace header) should clear focused_thread.
multi_workspace.update_in(cx, |mw, window, cx| {
if let Some(index) = mw.workspaces().iter().position(|w| w == &workspace_b) {
- mw.activate_index(index, window, cx);
+ let workspace = mw.workspaces()[index].clone();
+ mw.activate(workspace, window, cx);
}
});
cx.run_until_parked();
@@ -2317,7 +2321,8 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp
// Switch to the worktree workspace.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(1, window, cx);
+ let workspace = mw.workspaces()[1].clone();
+ mw.activate(workspace, window, cx);
});
let sidebar = setup_sidebar(&multi_workspace, cx);
@@ -2887,7 +2892,8 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp
// Switch back to the main workspace before setting up the sidebar.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
let sidebar = setup_sidebar(&multi_workspace, cx);
@@ -2993,7 +2999,8 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp
let worktree_panel = add_agent_panel(&worktree_workspace, &worktree_project, cx);
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
let sidebar = setup_sidebar(&multi_workspace, cx);
@@ -3338,7 +3345,8 @@ async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace(
// Activate the main workspace before setting up the sidebar.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
let sidebar = setup_sidebar(&multi_workspace, cx);
@@ -3422,7 +3430,8 @@ async fn test_activate_archived_thread_with_saved_paths_activates_matching_works
// Ensure workspace A is active.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
cx.run_until_parked();
assert_eq!(
@@ -3484,7 +3493,8 @@ async fn test_activate_archived_thread_cwd_fallback_with_matching_workspace(
// Start with workspace A active.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
cx.run_until_parked();
assert_eq!(
@@ -3545,7 +3555,8 @@ async fn test_activate_archived_thread_no_paths_no_cwd_uses_active_workspace(
// Activate workspace B (index 1) to make it the active one.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(1, window, cx);
+ let workspace = mw.workspaces()[1].clone();
+ mw.activate(workspace, window, cx);
});
cx.run_until_parked();
assert_eq!(
@@ -3928,7 +3939,8 @@ async fn test_archive_thread_uses_next_threads_own_workspace(cx: &mut TestAppCon
// Activate main workspace so the sidebar tracks the main panel.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
});
let sidebar = setup_sidebar(&multi_workspace, cx);
@@ -4784,9 +4796,11 @@ mod property_test {
state.workspace_paths.push(worktree.path);
}
Operation::RemoveWorkspace { index } => {
- let removed = multi_workspace
- .update_in(cx, |mw, window, cx| mw.remove_workspace(index, window, cx));
- if removed.is_some() {
+ let removed = multi_workspace.update_in(cx, |mw, window, cx| {
+ let workspace = mw.workspaces()[index].clone();
+ mw.remove(&workspace, window, cx)
+ });
+ if removed {
state.workspace_paths.remove(index);
state.main_repo_indices.retain(|i| *i != index);
for i in &mut state.main_repo_indices {
@@ -4799,8 +4813,8 @@ mod property_test {
Operation::SwitchWorkspace { index } => {
let workspace =
multi_workspace.read_with(cx, |mw, _| mw.workspaces()[index].clone());
- multi_workspace.update_in(cx, |mw, _window, cx| {
- mw.activate(workspace, cx);
+ multi_workspace.update_in(cx, |mw, window, cx| {
+ mw.activate(workspace, window, cx);
});
}
Operation::AddLinkedWorktree { workspace_index } => {
@@ -24,8 +24,8 @@ use ui::{ContextMenu, right_click_menu};
const SIDEBAR_RESIZE_HANDLE_SIZE: Pixels = px(6.0);
use crate::{
- CloseIntent, CloseWindow, DockPosition, Event as WorkspaceEvent, Item, ModalView, Panel,
- Workspace, WorkspaceId, client_side_decorations,
+ CloseIntent, CloseWindow, DockPosition, Event as WorkspaceEvent, Item, ModalView, OpenMode,
+ Panel, Workspace, WorkspaceId, client_side_decorations,
};
actions!(
@@ -233,7 +233,7 @@ impl MultiWorkspace {
this.close_sidebar(window, cx);
}
});
- Self::subscribe_to_workspace(&workspace, cx);
+ Self::subscribe_to_workspace(&workspace, window, cx);
let weak_self = cx.weak_entity();
workspace.update(cx, |workspace, cx| {
workspace.set_multi_workspace(weak_self, cx);
@@ -398,10 +398,14 @@ impl MultiWorkspace {
.detach_and_log_err(cx);
}
- fn subscribe_to_workspace(workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
- cx.subscribe(workspace, |this, workspace, event, cx| {
+ fn subscribe_to_workspace(
+ workspace: &Entity<Workspace>,
+ window: &Window,
+ cx: &mut Context<Self>,
+ ) {
+ cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| {
if let WorkspaceEvent::Activate = event {
- this.activate(workspace, cx);
+ this.activate(workspace.clone(), window, cx);
}
})
.detach();
@@ -419,54 +423,107 @@ impl MultiWorkspace {
self.active_workspace_index
}
- pub fn activate(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) {
+ /// Adds a workspace to this window without changing which workspace is
+ /// active.
+ pub fn add(&mut self, workspace: Entity<Workspace>, window: &Window, cx: &mut Context<Self>) {
if !self.multi_workspace_enabled(cx) {
- self.workspaces[0] = workspace;
- self.active_workspace_index = 0;
- cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
- cx.notify();
+ self.set_single_workspace(workspace, cx);
return;
}
- let old_index = self.active_workspace_index;
- let new_index = self.set_active_workspace(workspace, cx);
- if old_index != new_index {
- self.serialize(cx);
- }
+ self.insert_workspace(workspace, window, cx);
}
- fn set_active_workspace(
+ /// Ensures the workspace is in the multiworkspace and makes it the active one.
+ pub fn activate(
&mut self,
workspace: Entity<Workspace>,
+ window: &mut Window,
cx: &mut Context<Self>,
- ) -> usize {
- let index = self.add_workspace(workspace, cx);
+ ) {
+ if !self.multi_workspace_enabled(cx) {
+ self.set_single_workspace(workspace, cx);
+ return;
+ }
+
+ let index = self.insert_workspace(workspace, &*window, cx);
let changed = self.active_workspace_index != index;
self.active_workspace_index = index;
if changed {
cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
+ self.serialize(cx);
}
+ self.focus_active_workspace(window, cx);
+ cx.notify();
+ }
+
+ /// Replaces the currently active workspace with a new one. If the
+ /// workspace is already in the list, this just switches to it.
+ pub fn replace(
+ &mut self,
+ workspace: Entity<Workspace>,
+ window: &Window,
+ cx: &mut Context<Self>,
+ ) {
+ if !self.multi_workspace_enabled(cx) {
+ self.set_single_workspace(workspace, cx);
+ return;
+ }
+
+ if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
+ let changed = self.active_workspace_index != index;
+ self.active_workspace_index = index;
+ if changed {
+ cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
+ self.serialize(cx);
+ }
+ cx.notify();
+ return;
+ }
+
+ let old_workspace = std::mem::replace(
+ &mut self.workspaces[self.active_workspace_index],
+ workspace.clone(),
+ );
+
+ let old_entity_id = old_workspace.entity_id();
+ self.detach_workspace(&old_workspace, cx);
+
+ Self::subscribe_to_workspace(&workspace, window, cx);
+ self.sync_sidebar_to_workspace(&workspace, cx);
+
+ cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(old_entity_id));
+ cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace));
+ cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
+ self.serialize(cx);
+ cx.notify();
+ }
+
+ fn set_single_workspace(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) {
+ self.workspaces[0] = workspace;
+ self.active_workspace_index = 0;
+ cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
cx.notify();
- index
}
- /// Adds a workspace to this window without changing which workspace is active.
- /// Returns the index of the workspace (existing or newly inserted).
- pub fn add_workspace(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) -> usize {
+ /// Inserts a workspace into the list if not already present. Returns the
+ /// index of the workspace (existing or newly inserted). Does not change
+ /// the active workspace index.
+ fn insert_workspace(
+ &mut self,
+ workspace: Entity<Workspace>,
+ window: &Window,
+ cx: &mut Context<Self>,
+ ) -> usize {
if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
index
} else {
- if self.sidebar_open {
- let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
- workspace.update(cx, |workspace, _cx| {
- workspace.set_sidebar_focus_handle(sidebar_focus_handle);
- });
- }
+ Self::subscribe_to_workspace(&workspace, window, cx);
+ self.sync_sidebar_to_workspace(&workspace, cx);
let weak_self = cx.weak_entity();
workspace.update(cx, |workspace, cx| {
workspace.set_multi_workspace(weak_self, cx);
});
- Self::subscribe_to_workspace(&workspace, cx);
self.workspaces.push(workspace.clone());
cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace));
cx.notify();
@@ -474,19 +531,35 @@ impl MultiWorkspace {
}
}
- pub fn activate_index(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
- debug_assert!(
- index < self.workspaces.len(),
- "workspace index out of bounds"
- );
- let changed = self.active_workspace_index != index;
- self.active_workspace_index = index;
- self.serialize(cx);
- self.focus_active_workspace(window, cx);
- if changed {
- cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
+ /// Clears session state and DB binding for a workspace that is being
+ /// removed or replaced. The DB row is preserved so the workspace still
+ /// appears in the recent-projects list.
+ fn detach_workspace(&mut self, workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
+ workspace.update(cx, |workspace, _cx| {
+ workspace.session_id.take();
+ workspace._schedule_serialize_workspace.take();
+ workspace._serialize_workspace_task.take();
+ });
+
+ if let Some(workspace_id) = workspace.read(cx).database_id() {
+ let db = crate::persistence::WorkspaceDb::global(cx);
+ self.pending_removal_tasks.retain(|task| !task.is_ready());
+ self.pending_removal_tasks
+ .push(cx.background_spawn(async move {
+ db.set_session_binding(workspace_id, None, None)
+ .await
+ .log_err();
+ }));
+ }
+ }
+
+ fn sync_sidebar_to_workspace(&self, workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
+ if self.sidebar_open {
+ let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
+ workspace.update(cx, |workspace, _| {
+ workspace.set_sidebar_focus_handle(sidebar_focus_handle);
+ });
}
- cx.notify();
}
fn cycle_workspace(&mut self, delta: isize, window: &mut Window, cx: &mut Context<Self>) {
@@ -496,7 +569,8 @@ impl MultiWorkspace {
}
let current = self.active_workspace_index as isize;
let next = ((current + delta).rem_euclid(count)) as usize;
- self.activate_index(next, window, cx);
+ let workspace = self.workspaces[next].clone();
+ self.activate(workspace, window, cx);
}
fn next_workspace(&mut self, _: &NextWorkspace, window: &mut Window, cx: &mut Context<Self>) {
@@ -666,7 +740,7 @@ impl MultiWorkspace {
cx: &mut Context<Self>,
) -> Entity<Workspace> {
let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
- self.activate(workspace.clone(), cx);
+ self.activate(workspace.clone(), window, cx);
workspace
}
@@ -688,8 +762,7 @@ impl MultiWorkspace {
cx,
);
let new_workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
- self.set_active_workspace(new_workspace.clone(), cx);
- self.focus_active_workspace(window, cx);
+ self.activate(new_workspace.clone(), window, cx);
let weak_workspace = new_workspace.downgrade();
let db = crate::persistence::WorkspaceDb::global(cx);
@@ -716,14 +789,17 @@ impl MultiWorkspace {
})
}
- pub fn remove_workspace(
+ pub fn remove(
&mut self,
- index: usize,
+ workspace: &Entity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
- ) -> Option<Entity<Workspace>> {
- if self.workspaces.len() <= 1 || index >= self.workspaces.len() {
- return None;
+ ) -> bool {
+ let Some(index) = self.workspaces.iter().position(|w| w == workspace) else {
+ return false;
+ };
+ if self.workspaces.len() <= 1 {
+ return false;
}
let removed_workspace = self.workspaces.remove(index);
@@ -734,28 +810,7 @@ impl MultiWorkspace {
self.active_workspace_index -= 1;
}
- // Clear session_id and cancel any in-flight serialization on the
- // removed workspace. Without this, a pending throttle timer from
- // `serialize_workspace` could fire and write the old session_id
- // back to the DB, resurrecting the workspace on next launch.
- removed_workspace.update(cx, |workspace, _cx| {
- workspace.session_id.take();
- workspace._schedule_serialize_workspace.take();
- workspace._serialize_workspace_task.take();
- });
-
- if let Some(workspace_id) = removed_workspace.read(cx).database_id() {
- let db = crate::persistence::WorkspaceDb::global(cx);
- self.pending_removal_tasks.retain(|task| !task.is_ready());
- self.pending_removal_tasks
- .push(cx.background_spawn(async move {
- // Clear the session binding instead of deleting the row so
- // the workspace still appears in the recent-projects list.
- db.set_session_binding(workspace_id, None, None)
- .await
- .log_err();
- }));
- }
+ self.detach_workspace(&removed_workspace, cx);
self.serialize(cx);
self.focus_active_workspace(window, cx);
@@ -765,23 +820,20 @@ impl MultiWorkspace {
cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
cx.notify();
- Some(removed_workspace)
+ true
}
pub fn move_workspace_to_new_window(
&mut self,
- index: usize,
+ workspace: &Entity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) {
- if self.workspaces.len() <= 1 || index >= self.workspaces.len() {
+ let workspace = workspace.clone();
+ if !self.remove(&workspace, window, cx) {
return;
}
- let Some(workspace) = self.remove_workspace(index, window, cx) else {
- return;
- };
-
let app_state: Arc<crate::AppState> = workspace.read(cx).app_state().clone();
cx.defer(move |cx| {
@@ -805,23 +857,28 @@ impl MultiWorkspace {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let index = self.active_workspace_index;
- self.move_workspace_to_new_window(index, window, cx);
+ let workspace = self.workspace().clone();
+ self.move_workspace_to_new_window(&workspace, window, cx);
}
pub fn open_project(
&mut self,
paths: Vec<PathBuf>,
+ open_mode: OpenMode,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Workspace>>> {
let workspace = self.workspace().clone();
- if self.multi_workspace_enabled(cx) {
- workspace.update(cx, |workspace, cx| {
- workspace.open_workspace_for_paths(true, paths, window, cx)
- })
+ let needs_close_prompt =
+ open_mode == OpenMode::Replace || !self.multi_workspace_enabled(cx);
+ let open_mode = if self.multi_workspace_enabled(cx) {
+ open_mode
} else {
+ OpenMode::Replace
+ };
+
+ if needs_close_prompt {
cx.spawn_in(window, async move |_this, cx| {
let should_continue = workspace
.update_in(cx, |workspace, window, cx| {
@@ -831,13 +888,17 @@ impl MultiWorkspace {
if should_continue {
workspace
.update_in(cx, |workspace, window, cx| {
- workspace.open_workspace_for_paths(true, paths, window, cx)
+ workspace.open_workspace_for_paths(open_mode, paths, window, cx)
})?
.await
} else {
Ok(workspace)
}
})
+ } else {
+ workspace.update(cx, |workspace, cx| {
+ workspace.open_workspace_for_paths(open_mode, paths, window, cx)
+ })
}
}
}
@@ -1091,4 +1152,90 @@ mod tests {
);
});
}
+
+ #[gpui::test]
+ async fn test_replace(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ let project_a = Project::test(fs.clone(), [], cx).await;
+ let project_b = Project::test(fs.clone(), [], cx).await;
+ let project_c = Project::test(fs.clone(), [], cx).await;
+ let project_d = Project::test(fs.clone(), [], cx).await;
+
+ let (multi_workspace, cx) = cx
+ .add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
+
+ let workspace_a_id =
+ multi_workspace.read_with(cx, |mw, _cx| mw.workspaces()[0].entity_id());
+
+ // Replace the only workspace (single-workspace case).
+ let workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
+ let workspace = cx.new(|cx| Workspace::test_new(project_b.clone(), window, cx));
+ mw.replace(workspace.clone(), &*window, cx);
+ workspace
+ });
+
+ multi_workspace.read_with(cx, |mw, _cx| {
+ assert_eq!(mw.workspaces().len(), 1);
+ assert_eq!(
+ mw.workspaces()[0].entity_id(),
+ workspace_b.entity_id(),
+ "slot should now be project_b"
+ );
+ assert_ne!(
+ mw.workspaces()[0].entity_id(),
+ workspace_a_id,
+ "project_a should be gone"
+ );
+ });
+
+ // Add project_c as a second workspace, then replace it with project_d.
+ let workspace_c = multi_workspace.update_in(cx, |mw, window, cx| {
+ mw.test_add_workspace(project_c.clone(), window, cx)
+ });
+
+ multi_workspace.read_with(cx, |mw, _cx| {
+ assert_eq!(mw.workspaces().len(), 2);
+ assert_eq!(mw.active_workspace_index(), 1);
+ });
+
+ let workspace_d = multi_workspace.update_in(cx, |mw, window, cx| {
+ let workspace = cx.new(|cx| Workspace::test_new(project_d.clone(), window, cx));
+ mw.replace(workspace.clone(), &*window, cx);
+ workspace
+ });
+
+ multi_workspace.read_with(cx, |mw, _cx| {
+ assert_eq!(mw.workspaces().len(), 2, "should still have 2 workspaces");
+ assert_eq!(mw.active_workspace_index(), 1);
+ assert_eq!(
+ mw.workspaces()[1].entity_id(),
+ workspace_d.entity_id(),
+ "active slot should now be project_d"
+ );
+ assert_ne!(
+ mw.workspaces()[1].entity_id(),
+ workspace_c.entity_id(),
+ "project_c should be gone"
+ );
+ });
+
+ // Replace with workspace_b which is already in the list — should just switch.
+ multi_workspace.update_in(cx, |mw, window, cx| {
+ mw.replace(workspace_b.clone(), &*window, cx);
+ });
+
+ multi_workspace.read_with(cx, |mw, _cx| {
+ assert_eq!(
+ mw.workspaces().len(),
+ 2,
+ "no workspace should be added or removed"
+ );
+ assert_eq!(
+ mw.active_workspace_index(),
+ 0,
+ "should have switched to workspace_b"
+ );
+ });
+ }
}
@@ -2523,7 +2523,7 @@ mod tests {
let workspace2 = multi_workspace.update_in(cx, |mw, window, cx| {
let workspace = cx.new(|cx| crate::Workspace::test_new(project2.clone(), window, cx));
workspace.update(cx, |ws, _cx| ws.set_random_database_id());
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
workspace
});
@@ -2541,7 +2541,8 @@ mod tests {
// --- Remove the second workspace (index 1) ---
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.remove_workspace(1, window, cx);
+ let ws = mw.workspaces()[1].clone();
+ mw.remove(&ws, window, cx);
});
cx.run_until_parked();
@@ -4193,7 +4194,7 @@ mod tests {
workspace.update(cx, |ws: &mut crate::Workspace, _cx| {
ws.set_database_id(workspace2_db_id)
});
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
// Save a full workspace row to the DB directly.
@@ -4221,7 +4222,8 @@ mod tests {
// Remove workspace at index 1 (the second workspace).
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.remove_workspace(1, window, cx);
+ let ws = mw.workspaces()[1].clone();
+ mw.remove(&ws, window, cx);
});
cx.run_until_parked();
@@ -4291,7 +4293,7 @@ mod tests {
workspace.update(cx, |ws: &mut crate::Workspace, _cx| {
ws.set_database_id(ws2_id)
});
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
let session_id = "test-zombie-session";
@@ -4331,7 +4333,8 @@ mod tests {
// Remove workspace2 (index 1).
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.remove_workspace(1, window, cx);
+ let ws = mw.workspaces()[1].clone();
+ mw.remove(&ws, window, cx);
});
cx.run_until_parked();
@@ -4390,7 +4393,7 @@ mod tests {
workspace.update(cx, |ws: &mut crate::Workspace, _cx| {
ws.set_database_id(workspace2_db_id)
});
- mw.activate(workspace.clone(), cx);
+ mw.activate(workspace.clone(), window, cx);
});
// Save a full workspace row to the DB directly and let it settle.
@@ -4414,7 +4417,8 @@ mod tests {
// Remove workspace2 — this pushes a task to pending_removal_tasks.
multi_workspace.update_in(cx, |mw, window, cx| {
- mw.remove_workspace(1, window, cx);
+ let ws = mw.workspaces()[1].clone();
+ mw.remove(&ws, window, cx);
});
// Simulate the quit handler pattern: collect flush tasks + pending
@@ -1,5 +1,5 @@
use crate::{
- NewFile, Open, PathList, SerializedWorkspaceLocation, Workspace, WorkspaceId,
+ NewFile, Open, OpenMode, PathList, SerializedWorkspaceLocation, Workspace, WorkspaceId,
item::{Item, ItemEvent},
persistence::WorkspaceDb,
};
@@ -326,7 +326,7 @@ impl WelcomePage {
self.workspace
.update(cx, |workspace, cx| {
workspace
- .open_workspace_for_paths(true, paths, window, cx)
+ .open_workspace_for_paths(OpenMode::Replace, paths, window, cx)
.detach_and_log_err(cx);
})
.log_err();
@@ -664,7 +664,15 @@ fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, c
})
.ok();
} else {
- let task = Workspace::new_local(Vec::new(), app_state.clone(), None, None, None, true, cx);
+ let task = Workspace::new_local(
+ Vec::new(),
+ app_state.clone(),
+ None,
+ None,
+ None,
+ OpenMode::Replace,
+ cx,
+ );
cx.spawn(async move |cx| {
let OpenResult { window, .. } = task.await?;
window.update(cx, |multi_workspace, window, cx| {
@@ -703,7 +711,7 @@ pub fn prompt_for_open_path_and_open(
if let Some(handle) = multi_workspace_handle {
if let Some(task) = handle
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.open_project(paths, window, cx)
+ multi_workspace.open_project(paths, OpenMode::Replace, window, cx)
})
.log_err()
{
@@ -714,7 +722,7 @@ pub fn prompt_for_open_path_and_open(
}
if let Some(task) = this
.update_in(cx, |this, window, cx| {
- this.open_workspace_for_paths(false, paths, window, cx)
+ this.open_workspace_for_paths(OpenMode::NewWindow, paths, window, cx)
})
.log_err()
{
@@ -1359,6 +1367,19 @@ struct FollowerView {
location: Option<proto::PanelId>,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum OpenMode {
+ /// Open the workspace in a new window.
+ NewWindow,
+ /// Add to the window's multi workspace without activating it (used during deserialization).
+ Add,
+ /// Add to the window's multi workspace and activate it.
+ #[default]
+ Activate,
+ /// Replace the currently active workspace, and any of it's linked workspaces
+ Replace,
+}
+
impl Workspace {
pub fn new(
workspace_id: Option<WorkspaceId>,
@@ -1764,7 +1785,7 @@ impl Workspace {
requesting_window: Option<WindowHandle<MultiWorkspace>>,
env: Option<HashMap<String, String>>,
init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
- activate: bool,
+ open_mode: OpenMode,
cx: &mut App,
) -> Task<anyhow::Result<OpenResult>> {
let project_handle = Project::local(
@@ -1862,8 +1883,13 @@ impl Workspace {
});
}
+ let window_to_replace = match open_mode {
+ OpenMode::NewWindow => None,
+ _ => requesting_window,
+ };
+
let (window, workspace): (WindowHandle<MultiWorkspace>, Entity<Workspace>) =
- if let Some(window) = requesting_window {
+ if let Some(window) = window_to_replace {
let centered_layout = serialized_workspace
.as_ref()
.map(|w| w.centered_layout)
@@ -1888,10 +1914,19 @@ impl Workspace {
workspace
});
- if activate {
- multi_workspace.activate(workspace.clone(), cx);
- } else {
- multi_workspace.add_workspace(workspace.clone(), cx);
+ match open_mode {
+ OpenMode::Replace => {
+ multi_workspace.replace(workspace.clone(), &*window, cx);
+ }
+ OpenMode::Activate => {
+ multi_workspace.activate(workspace.clone(), window, cx);
+ }
+ OpenMode::Add => {
+ multi_workspace.add(workspace.clone(), &*window, cx);
+ }
+ OpenMode::NewWindow => {
+ unreachable!()
+ }
}
workspace
})?;
@@ -2921,7 +2956,7 @@ impl Workspace {
None,
env,
None,
- true,
+ OpenMode::Activate,
cx,
);
cx.spawn_in(window, async move |_vh, cx| {
@@ -2962,7 +2997,7 @@ impl Workspace {
None,
env,
None,
- true,
+ OpenMode::Activate,
cx,
);
cx.spawn_in(window, async move |_vh, cx| {
@@ -3344,23 +3379,22 @@ impl Workspace {
pub fn open_workspace_for_paths(
&mut self,
- replace_current_window: bool,
+ // replace_current_window: bool,
+ mut open_mode: OpenMode,
paths: Vec<PathBuf>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Workspace>>> {
- let window_handle = window.window_handle().downcast::<MultiWorkspace>();
+ let requesting_window = window.window_handle().downcast::<MultiWorkspace>();
let is_remote = self.project.read(cx).is_via_collab();
let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
- let window_to_replace = if replace_current_window {
- window_handle
- } else if is_remote || has_worktree || has_dirty_items {
- None
- } else {
- window_handle
- };
+ let workspace_is_empty = !is_remote && !has_worktree && !has_dirty_items;
+ if workspace_is_empty {
+ open_mode = OpenMode::Replace;
+ }
+
let app_state = self.app_state.clone();
cx.spawn(async move |_, cx| {
@@ -3370,7 +3404,8 @@ impl Workspace {
&paths,
app_state,
OpenOptions {
- replace_window: window_to_replace,
+ requesting_window,
+ open_mode,
..Default::default()
},
cx,
@@ -8578,7 +8613,7 @@ pub async fn restore_multiworkspace(
None,
None,
None,
- true,
+ OpenMode::Activate,
cx,
)
})
@@ -8608,7 +8643,7 @@ pub async fn restore_multiworkspace(
Some(window_handle),
None,
None,
- false,
+ OpenMode::Add,
cx,
)
})
@@ -8628,18 +8663,17 @@ pub async fn restore_multiworkspace(
.workspaces()
.iter()
.position(|ws| ws.read(cx).database_id() == Some(target_id));
- if let Some(index) = target_index {
- multi_workspace.activate_index(index, window, cx);
- } else if !multi_workspace.workspaces().is_empty() {
- multi_workspace.activate_index(0, window, cx);
+ let index = target_index.unwrap_or(0);
+ if let Some(workspace) = multi_workspace.workspaces().get(index).cloned() {
+ multi_workspace.activate(workspace, window, cx);
}
})
.ok();
} else {
window_handle
.update(cx, |multi_workspace, window, cx| {
- if !multi_workspace.workspaces().is_empty() {
- multi_workspace.activate_index(0, window, cx);
+ if let Some(workspace) = multi_workspace.workspaces().first().cloned() {
+ multi_workspace.activate(workspace, window, cx);
}
})
.ok();
@@ -8890,7 +8924,7 @@ pub fn join_channel(
requesting_window,
None,
None,
- true,
+ OpenMode::Activate,
cx,
)
})
@@ -8963,8 +8997,18 @@ pub async fn get_any_active_multi_workspace(
// find an existing workspace to focus and show call controls
let active_window = activate_any_workspace_window(&mut cx);
if active_window.is_none() {
- cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, None, true, cx))
- .await?;
+ cx.update(|cx| {
+ Workspace::new_local(
+ vec![],
+ app_state.clone(),
+ None,
+ None,
+ None,
+ OpenMode::Activate,
+ cx,
+ )
+ })
+ .await?;
}
activate_any_workspace_window(&mut cx).context("could not open zed")
}
@@ -9134,7 +9178,8 @@ pub struct OpenOptions {
pub focus: Option<bool>,
pub open_new_workspace: Option<bool>,
pub wait: bool,
- pub replace_window: Option<WindowHandle<MultiWorkspace>>,
+ pub requesting_window: Option<WindowHandle<MultiWorkspace>>,
+ pub open_mode: OpenMode,
pub env: Option<HashMap<String, String>>,
}
@@ -9189,7 +9234,7 @@ pub fn open_workspace_by_id(
workspace.centered_layout = centered_layout;
workspace
});
- multi_workspace.add_workspace(workspace.clone(), cx);
+ multi_workspace.add(workspace.clone(), &*window, cx);
workspace
})?;
(window, workspace)
@@ -9319,7 +9364,7 @@ pub fn open_paths(
let open_task = existing
.update(cx, |multi_workspace, window, cx| {
window.activate_window();
- multi_workspace.activate(target_workspace.clone(), cx);
+ multi_workspace.activate(target_workspace.clone(), window, cx);
target_workspace.update(cx, |workspace, cx| {
workspace.open_paths(
abs_paths,
@@ -9353,10 +9398,10 @@ pub fn open_paths(
Workspace::new_local(
abs_paths,
app_state.clone(),
- open_options.replace_window,
+ open_options.requesting_window,
open_options.env,
None,
- true,
+ open_options.open_mode,
cx,
)
})
@@ -9414,13 +9459,14 @@ pub fn open_new(
cx: &mut App,
init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
) -> Task<anyhow::Result<()>> {
+ let addition = open_options.open_mode;
let task = Workspace::new_local(
Vec::new(),
app_state,
- open_options.replace_window,
+ open_options.requesting_window,
open_options.env,
Some(Box::new(init)),
- true,
+ addition,
cx,
);
cx.spawn(async move |cx| {
@@ -9631,7 +9677,7 @@ async fn open_remote_project_inner(
workspace
});
- multi_workspace.activate(new_workspace.clone(), cx);
+ multi_workspace.activate(new_workspace.clone(), window, cx);
new_workspace
})?;
@@ -9718,8 +9764,8 @@ pub fn join_in_room_project(
existing_window_and_workspace
{
existing_window
- .update(cx, |multi_workspace, _, cx| {
- multi_workspace.activate(target_workspace, cx);
+ .update(cx, |multi_workspace, window, cx| {
+ multi_workspace.activate(target_workspace, window, cx);
})
.ok();
existing_window
@@ -10653,7 +10699,8 @@ mod tests {
// Activate workspace A
multi_workspace_handle
.update(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
})
.unwrap();
@@ -14422,7 +14469,8 @@ mod tests {
// Switch to workspace A
multi_workspace_handle
.update(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
})
.unwrap();
@@ -14467,7 +14515,8 @@ mod tests {
// Switch to workspace B
multi_workspace_handle
.update(cx, |mw, window, cx| {
- mw.activate_index(1, window, cx);
+ let workspace = mw.workspaces()[1].clone();
+ mw.activate(workspace, window, cx);
})
.unwrap();
cx.run_until_parked();
@@ -14475,7 +14524,8 @@ mod tests {
// Switch back to workspace A
multi_workspace_handle
.update(cx, |mw, window, cx| {
- mw.activate_index(0, window, cx);
+ let workspace = mw.workspaces()[0].clone();
+ mw.activate(workspace, window, cx);
})
.unwrap();
cx.run_until_parked();
@@ -2594,7 +2594,7 @@ fn run_multi_workspace_sidebar_visual_tests(
});
cx.new(|cx| {
let mut multi_workspace = MultiWorkspace::new(workspace1, window, cx);
- multi_workspace.activate(workspace2, cx);
+ multi_workspace.activate(workspace2, window, cx);
multi_workspace
})
},
@@ -2645,7 +2645,8 @@ fn run_multi_workspace_sidebar_visual_tests(
// Switch to workspace 1 so it's highlighted as active (index 0)
multi_workspace_window
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.activate_index(0, window, cx);
+ let workspace = multi_workspace.workspaces()[0].clone();
+ multi_workspace.activate(workspace, window, cx);
})
.context("Failed to activate workspace 1")?;
@@ -1121,7 +1121,7 @@ fn register_actions(
let task = cx.update(|_window, cx| {
open_new(
workspace::OpenOptions {
- replace_window: Some(window_handle),
+ requesting_window: Some(window_handle),
..Default::default()
},
app_state,
@@ -1375,7 +1375,7 @@ fn quit(_: &Quit, cx: &mut App) {
for workspace in workspaces {
if let Some(should_close) = window
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.activate(workspace.clone(), cx);
+ multi_workspace.activate(workspace.clone(), window, cx);
window.activate_window();
workspace.update(cx, |workspace, cx| {
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
@@ -2456,7 +2456,7 @@ mod tests {
&[PathBuf::from(path!("/root/e"))],
app_state,
workspace::OpenOptions {
- replace_window: Some(window),
+ requesting_window: Some(window),
..Default::default()
},
cx,
@@ -5372,11 +5372,11 @@ mod tests {
.unwrap();
window
- .update(cx, |multi_workspace, _, cx| {
- multi_workspace.activate(workspace2.clone(), cx);
- multi_workspace.activate(workspace3.clone(), cx);
+ .update(cx, |multi_workspace, window, cx| {
+ multi_workspace.activate(workspace2.clone(), window, cx);
+ multi_workspace.activate(workspace3.clone(), window, cx);
// Switch back to workspace1 for test setup
- multi_workspace.activate(workspace1, cx);
+ multi_workspace.activate(workspace1, window, cx);
assert_eq!(multi_workspace.active_workspace_index(), 0);
})
.unwrap();
@@ -5558,9 +5558,9 @@ mod tests {
.unwrap();
window1
- .update(cx, |multi_workspace, _, cx| {
- multi_workspace.activate(workspace1_2.clone(), cx);
- multi_workspace.activate(workspace1_1.clone(), cx);
+ .update(cx, |multi_workspace, window, cx| {
+ multi_workspace.activate(workspace1_2.clone(), window, cx);
+ multi_workspace.activate(workspace1_1.clone(), window, cx);
})
.unwrap();
@@ -5791,7 +5791,7 @@ mod tests {
async fn test_multi_workspace_session_restore(cx: &mut TestAppContext) {
use collections::HashMap;
use session::Session;
- use workspace::{Workspace, WorkspaceId};
+ use workspace::{OpenMode, Workspace, WorkspaceId};
let app_state = init_test(cx);
@@ -5826,7 +5826,7 @@ mod tests {
None,
None,
None,
- true,
+ OpenMode::Activate,
cx,
)
})
@@ -5835,7 +5835,7 @@ mod tests {
window_a
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.open_project(vec![dir2.into()], window, cx)
+ multi_workspace.open_project(vec![dir2.into()], OpenMode::Activate, window, cx)
})
.unwrap()
.await
@@ -5852,7 +5852,7 @@ mod tests {
None,
None,
None,
- true,
+ OpenMode::Activate,
cx,
)
})
@@ -5865,7 +5865,8 @@ mod tests {
// still be active rather than whichever workspace happened to restore last.
window_a
.update(cx, |multi_workspace, window, cx| {
- multi_workspace.activate_index(0, window, cx);
+ let workspace = multi_workspace.workspaces()[0].clone();
+ multi_workspace.activate(workspace, window, cx);
})
.unwrap();
@@ -529,7 +529,7 @@ async fn open_workspaces(
};
let open_options = workspace::OpenOptions {
open_new_workspace,
- replace_window,
+ requesting_window: replace_window,
wait,
env: env.clone(),
..Default::default()
@@ -1292,7 +1292,7 @@ mod tests {
vec![],
false,
workspace::OpenOptions {
- replace_window: Some(window_to_replace),
+ requesting_window: Some(window_to_replace),
..Default::default()
},
&response_tx,