Implement worktree interactions for the sidebar (#51421)
Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - N/A
This commit is contained in:
1 parent
d11fdd2ebe
commit
c90707e306
8 files changed
+399
-82
No files matched your search
@@ -87,7 +87,7 @@ use ui::{
|
||||
use util::{ResultExt as _, debug_panic};
|
||||
use workspace::{
|
||||
CollaboratorId, DraggedSelection, DraggedSidebar, DraggedTab, FocusWorkspaceSidebar,
|
||||
MultiWorkspace, SIDEBAR_RESIZE_HANDLE_SIZE, ToggleWorkspaceSidebar, ToggleZoom,
|
||||
MultiWorkspace, OpenResult, SIDEBAR_RESIZE_HANDLE_SIZE, ToggleWorkspaceSidebar, ToggleZoom,
|
||||
ToolbarItemView, Workspace, WorkspaceId,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
multi_workspace_enabled,
|
||||
@@ -3025,21 +3025,16 @@ impl AgentPanel {
|
||||
workspace.set_dock_structure(dock_structure, window, cx);
|
||||
}));
|
||||
|
||||
let (new_window_handle, _) = cx
|
||||
let OpenResult {
|
||||
window: new_window_handle,
|
||||
workspace: new_workspace,
|
||||
..
|
||||
} = cx
|
||||
.update(|_window, cx| {
|
||||
Workspace::new_local(all_paths, app_state, window_handle, None, init, false, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let new_workspace = new_window_handle.update(cx, |multi_workspace, _window, _cx| {
|
||||
let workspaces = multi_workspace.workspaces();
|
||||
workspaces.last().cloned()
|
||||
})?;
|
||||
|
||||
let Some(new_workspace) = new_workspace else {
|
||||
anyhow::bail!("New workspace was not added to MultiWorkspace");
|
||||
};
|
||||
|
||||
let panels_task = new_window_handle.update(cx, |_, _, cx| {
|
||||
new_workspace.update(cx, |workspace, _cx| workspace.take_panels_task())
|
||||
})?;
|
||||
|
||||
+305
-17
@@ -99,13 +99,19 @@ impl From<&ActiveThreadInfo> for acp_thread::AgentSessionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ThreadEntryWorkspace {
|
||||
Open(Entity<Workspace>),
|
||||
Closed(PathList),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ThreadEntry {
|
||||
session_info: acp_thread::AgentSessionInfo,
|
||||
icon: IconName,
|
||||
icon_from_external_svg: Option<SharedString>,
|
||||
status: AgentThreadStatus,
|
||||
workspace: Entity<Workspace>,
|
||||
workspace: ThreadEntryWorkspace,
|
||||
is_live: bool,
|
||||
is_background: bool,
|
||||
highlight_positions: Vec<usize>,
|
||||
@@ -528,7 +534,8 @@ impl Sidebar {
|
||||
// main repo's header instead of getting their own.
|
||||
let mut main_repo_workspace: HashMap<Arc<Path>, usize> = HashMap::new();
|
||||
let mut absorbed: HashMap<usize, (usize, SharedString)> = HashMap::new();
|
||||
let mut pending: HashMap<Arc<Path>, Vec<(usize, SharedString)>> = HashMap::new();
|
||||
let mut pending: HashMap<Arc<Path>, Vec<(usize, SharedString, Arc<Path>)>> = HashMap::new();
|
||||
let mut absorbed_workspace_by_path: HashMap<Arc<Path>, usize> = HashMap::new();
|
||||
|
||||
for (i, workspace) in workspaces.iter().enumerate() {
|
||||
for snapshot in root_repository_snapshots(workspace, cx) {
|
||||
@@ -537,8 +544,9 @@ impl Sidebar {
|
||||
.entry(snapshot.work_directory_abs_path.clone())
|
||||
.or_insert(i);
|
||||
if let Some(waiting) = pending.remove(&snapshot.work_directory_abs_path) {
|
||||
for (ws_idx, name) in waiting {
|
||||
for (ws_idx, name, ws_path) in waiting {
|
||||
absorbed.insert(ws_idx, (i, name));
|
||||
absorbed_workspace_by_path.insert(ws_path, ws_idx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -553,11 +561,13 @@ impl Sidebar {
|
||||
main_repo_workspace.get(&snapshot.original_repo_abs_path)
|
||||
{
|
||||
absorbed.insert(i, (main_idx, name));
|
||||
absorbed_workspace_by_path
|
||||
.insert(snapshot.work_directory_abs_path.clone(), i);
|
||||
} else {
|
||||
pending
|
||||
.entry(snapshot.original_repo_abs_path.clone())
|
||||
.or_default()
|
||||
.push((i, name));
|
||||
.push((i, name, snapshot.work_directory_abs_path.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,7 +596,7 @@ impl Sidebar {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::default(),
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: false,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -599,7 +609,8 @@ impl Sidebar {
|
||||
|
||||
// Load threads from linked git worktrees of this workspace's repos.
|
||||
if let Some(ref thread_store) = thread_store {
|
||||
let mut linked_worktree_queries: Vec<(PathList, SharedString)> = Vec::new();
|
||||
let mut linked_worktree_queries: Vec<(PathList, SharedString, Arc<Path>)> =
|
||||
Vec::new();
|
||||
for snapshot in root_repository_snapshots(workspace, cx) {
|
||||
if snapshot.work_directory_abs_path != snapshot.original_repo_abs_path {
|
||||
continue;
|
||||
@@ -614,11 +625,20 @@ impl Sidebar {
|
||||
linked_worktree_queries.push((
|
||||
PathList::new(std::slice::from_ref(&git_worktree.path)),
|
||||
name.into(),
|
||||
Arc::from(git_worktree.path.as_path()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (worktree_path_list, worktree_name) in &linked_worktree_queries {
|
||||
for (worktree_path_list, worktree_name, worktree_path) in
|
||||
&linked_worktree_queries
|
||||
{
|
||||
let target_workspace =
|
||||
match absorbed_workspace_by_path.get(worktree_path.as_ref()) {
|
||||
Some(&idx) => ThreadEntryWorkspace::Open(workspaces[idx].clone()),
|
||||
None => ThreadEntryWorkspace::Closed(worktree_path_list.clone()),
|
||||
};
|
||||
|
||||
for meta in thread_store.read(cx).threads_for_paths(worktree_path_list) {
|
||||
if !seen_session_ids.insert(meta.id.clone()) {
|
||||
continue;
|
||||
@@ -628,7 +648,7 @@ impl Sidebar {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::default(),
|
||||
workspace: workspace.clone(),
|
||||
workspace: target_workspace.clone(),
|
||||
is_live: false,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -1347,8 +1367,20 @@ impl Sidebar {
|
||||
}
|
||||
ListEntry::Thread(thread) => {
|
||||
let session_info = thread.session_info.clone();
|
||||
let workspace = thread.workspace.clone();
|
||||
self.activate_thread(session_info, &workspace, window, cx);
|
||||
match &thread.workspace {
|
||||
ThreadEntryWorkspace::Open(workspace) => {
|
||||
let workspace = workspace.clone();
|
||||
self.activate_thread(session_info, &workspace, window, cx);
|
||||
}
|
||||
ThreadEntryWorkspace::Closed(path_list) => {
|
||||
self.open_workspace_and_activate_thread(
|
||||
session_info,
|
||||
path_list.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ListEntry::ViewMore {
|
||||
path_list,
|
||||
@@ -1403,6 +1435,32 @@ impl Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_workspace_and_activate_thread(
|
||||
&mut self,
|
||||
session_info: acp_thread::AgentSessionInfo,
|
||||
path_list: PathList,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(multi_workspace) = self.multi_workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let workspace = open_task.await?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.activate_thread(session_info, &workspace, window, cx);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn expand_selected_entry(
|
||||
&mut self,
|
||||
_: &ExpandSelectedEntry,
|
||||
@@ -1480,7 +1538,7 @@ impl Sidebar {
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Untitled".into());
|
||||
let session_info = thread.session_info.clone();
|
||||
let workspace = thread.workspace.clone();
|
||||
let thread_workspace = thread.workspace.clone();
|
||||
|
||||
let id = SharedString::from(format!("thread-entry-{}", ix));
|
||||
|
||||
@@ -1533,7 +1591,19 @@ impl Sidebar {
|
||||
.docked_right(docked_right)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selection = None;
|
||||
this.activate_thread(session_info.clone(), &workspace, window, cx);
|
||||
match &thread_workspace {
|
||||
ThreadEntryWorkspace::Open(workspace) => {
|
||||
this.activate_thread(session_info.clone(), workspace, window, cx);
|
||||
}
|
||||
ThreadEntryWorkspace::Closed(path_list) => {
|
||||
this.open_workspace_and_activate_thread(
|
||||
session_info.clone(),
|
||||
path_list.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -2447,7 +2517,7 @@ mod tests {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::Completed,
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: false,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -2468,7 +2538,7 @@ mod tests {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::Running,
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: true,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -2489,7 +2559,7 @@ mod tests {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::Error,
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: true,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -2510,7 +2580,7 @@ mod tests {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::WaitingForConfirmation,
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: false,
|
||||
is_background: false,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -2531,7 +2601,7 @@ mod tests {
|
||||
icon: IconName::ZedAgent,
|
||||
icon_from_external_svg: None,
|
||||
status: AgentThreadStatus::Completed,
|
||||
workspace: workspace.clone(),
|
||||
workspace: ThreadEntryWorkspace::Open(workspace.clone()),
|
||||
is_live: true,
|
||||
is_background: true,
|
||||
highlight_positions: Vec::new(),
|
||||
@@ -4305,4 +4375,222 @@ mod tests {
|
||||
vec!["v [project]", " Thread A {wt-feature-a}",]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_clicking_worktree_thread_opens_workspace_when_none_exists(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
fs.insert_tree(
|
||||
"/project",
|
||||
serde_json::json!({
|
||||
".git": {
|
||||
"worktrees": {
|
||||
"feature-a": {
|
||||
"commondir": "../../",
|
||||
"HEAD": "ref: refs/heads/feature-a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.insert_tree(
|
||||
"/wt-feature-a",
|
||||
serde_json::json!({
|
||||
".git": "gitdir: /project/.git/worktrees/feature-a",
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
|
||||
state.worktrees.push(git::repository::Worktree {
|
||||
path: std::path::PathBuf::from("/wt-feature-a"),
|
||||
ref_name: "refs/heads/feature-a".into(),
|
||||
sha: "aaa".into(),
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
|
||||
|
||||
// Only open the main repo — no workspace for the worktree.
|
||||
let main_project = project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
|
||||
main_project
|
||||
.update(cx, |p, cx| p.git_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(main_project.clone(), window, cx)
|
||||
});
|
||||
let sidebar = setup_sidebar(&multi_workspace, cx);
|
||||
|
||||
// Save a thread for the worktree path (no workspace for it).
|
||||
let paths_wt = PathList::new(&[std::path::PathBuf::from("/wt-feature-a")]);
|
||||
save_named_thread("thread-wt", "WT Thread", &paths_wt, cx).await;
|
||||
|
||||
multi_workspace.update_in(cx, |_, _window, cx| cx.notify());
|
||||
cx.run_until_parked();
|
||||
|
||||
// Thread should appear under the main repo with a worktree chip.
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&sidebar, cx),
|
||||
vec!["v [project]", " WT Thread {wt-feature-a}"],
|
||||
);
|
||||
|
||||
// Only 1 workspace should exist.
|
||||
assert_eq!(
|
||||
multi_workspace.read_with(cx, |mw, _| mw.workspaces().len()),
|
||||
1,
|
||||
);
|
||||
|
||||
// Focus the sidebar and select the worktree thread.
|
||||
open_and_focus_sidebar(&sidebar, cx);
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(1); // index 0 is header, 1 is the thread
|
||||
});
|
||||
|
||||
// Confirm to open the worktree thread.
|
||||
cx.dispatch_action(Confirm);
|
||||
cx.run_until_parked();
|
||||
|
||||
// A new workspace should have been created for the worktree path.
|
||||
let new_workspace = multi_workspace.read_with(cx, |mw, _| {
|
||||
assert_eq!(
|
||||
mw.workspaces().len(),
|
||||
2,
|
||||
"confirming a worktree thread without a workspace should open one",
|
||||
);
|
||||
mw.workspaces()[1].clone()
|
||||
});
|
||||
|
||||
let (new_path_list, _) = new_workspace.read_with(cx, |_, cx| {
|
||||
workspace_path_list_and_label(&new_workspace, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
new_path_list,
|
||||
PathList::new(&[std::path::PathBuf::from("/wt-feature-a")]),
|
||||
"the new workspace should have been opened for the worktree path",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
fs.insert_tree(
|
||||
"/project",
|
||||
serde_json::json!({
|
||||
".git": {
|
||||
"worktrees": {
|
||||
"feature-a": {
|
||||
"commondir": "../../",
|
||||
"HEAD": "ref: refs/heads/feature-a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.insert_tree(
|
||||
"/wt-feature-a",
|
||||
serde_json::json!({
|
||||
".git": "gitdir: /project/.git/worktrees/feature-a",
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
|
||||
state.worktrees.push(git::repository::Worktree {
|
||||
path: std::path::PathBuf::from("/wt-feature-a"),
|
||||
ref_name: "refs/heads/feature-a".into(),
|
||||
sha: "aaa".into(),
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
|
||||
|
||||
let main_project = project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
|
||||
let worktree_project =
|
||||
project::Project::test(fs.clone(), ["/wt-feature-a".as_ref()], cx).await;
|
||||
|
||||
main_project
|
||||
.update(cx, |p, cx| p.git_scans_complete(cx))
|
||||
.await;
|
||||
worktree_project
|
||||
.update(cx, |p, cx| p.git_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
MultiWorkspace::test_new(main_project.clone(), window, cx)
|
||||
});
|
||||
|
||||
let worktree_workspace = multi_workspace.update_in(cx, |mw, window, cx| {
|
||||
mw.test_add_workspace(worktree_project.clone(), window, cx)
|
||||
});
|
||||
|
||||
// Activate the main workspace before setting up the sidebar.
|
||||
multi_workspace.update_in(cx, |mw, window, cx| {
|
||||
mw.activate_index(0, window, cx);
|
||||
});
|
||||
|
||||
let sidebar = setup_sidebar(&multi_workspace, cx);
|
||||
|
||||
let paths_main = PathList::new(&[std::path::PathBuf::from("/project")]);
|
||||
let paths_wt = PathList::new(&[std::path::PathBuf::from("/wt-feature-a")]);
|
||||
save_named_thread("thread-main", "Main Thread", &paths_main, cx).await;
|
||||
save_named_thread("thread-wt", "WT Thread", &paths_wt, cx).await;
|
||||
|
||||
multi_workspace.update_in(cx, |_, _window, cx| cx.notify());
|
||||
cx.run_until_parked();
|
||||
|
||||
// The worktree workspace should be absorbed under the main repo.
|
||||
let entries = visible_entries_as_strings(&sidebar, cx);
|
||||
assert_eq!(entries.len(), 3);
|
||||
assert_eq!(entries[0], "v [project]");
|
||||
assert!(entries.contains(&" Main Thread".to_string()));
|
||||
assert!(entries.contains(&" WT Thread {wt-feature-a}".to_string()));
|
||||
|
||||
let wt_thread_index = entries
|
||||
.iter()
|
||||
.position(|e| e.contains("WT Thread"))
|
||||
.expect("should find the worktree thread entry");
|
||||
|
||||
assert_eq!(
|
||||
multi_workspace.read_with(cx, |mw, _| mw.active_workspace_index()),
|
||||
0,
|
||||
"main workspace should be active initially"
|
||||
);
|
||||
|
||||
// Focus the sidebar and select the absorbed worktree thread.
|
||||
open_and_focus_sidebar(&sidebar, cx);
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(wt_thread_index);
|
||||
});
|
||||
|
||||
// Confirm to activate the worktree thread.
|
||||
cx.dispatch_action(Confirm);
|
||||
cx.run_until_parked();
|
||||
|
||||
// The worktree workspace should now be active, not the main one.
|
||||
let active_workspace = multi_workspace.read_with(cx, |mw, _| {
|
||||
mw.workspaces()[mw.active_workspace_index()].clone()
|
||||
});
|
||||
assert_eq!(
|
||||
active_workspace, worktree_workspace,
|
||||
"clicking an absorbed worktree thread should activate the worktree workspace"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use workspace::{AppState, OpenVisible, Workspace};
|
||||
use workspace::{AppState, OpenResult, OpenVisible, Workspace};
|
||||
|
||||
actions!(
|
||||
journal,
|
||||
@@ -107,7 +107,10 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap
|
||||
.spawn(cx, async move |cx| {
|
||||
let (journal_dir, entry_path) = create_entry.await?;
|
||||
let opened = if open_new_workspace {
|
||||
let (new_workspace, _) = cx
|
||||
let OpenResult {
|
||||
window: new_workspace,
|
||||
..
|
||||
} = cx
|
||||
.update(|_window, cx| {
|
||||
workspace::open_paths(
|
||||
&[journal_dir],
|
||||
|
||||
@@ -935,7 +935,14 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
workspace.open_workspace_for_paths(false, paths, window, cx)
|
||||
workspace
|
||||
.open_workspace_for_paths(false, paths, window, cx)
|
||||
.detach_and_prompt_err(
|
||||
"Failed to open project",
|
||||
window,
|
||||
cx,
|
||||
|_, _, _| None,
|
||||
);
|
||||
}
|
||||
}
|
||||
SerializedWorkspaceLocation::Remote(mut connection) => {
|
||||
@@ -964,14 +971,14 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach_and_prompt_err(
|
||||
"Failed to open project",
|
||||
window,
|
||||
cx,
|
||||
|_, _, _| None,
|
||||
);
|
||||
}
|
||||
}
|
||||
.detach_and_prompt_err(
|
||||
"Failed to open project",
|
||||
window,
|
||||
cx,
|
||||
|_, _, _| None,
|
||||
);
|
||||
});
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ impl MultiWorkspace {
|
||||
paths: Vec<PathBuf>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
let workspace = self.workspace().clone();
|
||||
|
||||
if multi_workspace_enabled(cx) {
|
||||
@@ -519,7 +519,7 @@ impl MultiWorkspace {
|
||||
})?
|
||||
.await
|
||||
} else {
|
||||
Ok(())
|
||||
Ok(workspace)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, c
|
||||
} else {
|
||||
let task = Workspace::new_local(Vec::new(), app_state.clone(), None, None, None, true, cx);
|
||||
cx.spawn(async move |cx| {
|
||||
let (window, _) = task.await?;
|
||||
let OpenResult { window, .. } = task.await?;
|
||||
window.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
@@ -1752,12 +1752,7 @@ impl Workspace {
|
||||
init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
|
||||
activate: bool,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
anyhow::Result<(
|
||||
WindowHandle<MultiWorkspace>,
|
||||
Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<anyhow::Result<OpenResult>> {
|
||||
let project_handle = Project::local(
|
||||
app_state.client.clone(),
|
||||
app_state.node_runtime.clone(),
|
||||
@@ -1997,7 +1992,11 @@ impl Workspace {
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
Ok((window, opened_items))
|
||||
Ok(OpenResult {
|
||||
window,
|
||||
workspace,
|
||||
opened_items,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2685,7 +2684,10 @@ impl Workspace {
|
||||
cx,
|
||||
);
|
||||
cx.spawn_in(window, async move |_vh, cx| {
|
||||
let (multi_workspace_window, _) = task.await?;
|
||||
let OpenResult {
|
||||
window: multi_workspace_window,
|
||||
..
|
||||
} = task.await?;
|
||||
multi_workspace_window.update(cx, |multi_workspace, window, cx| {
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
workspace.update(cx, |workspace, cx| callback(workspace, window, cx))
|
||||
@@ -2723,7 +2725,10 @@ impl Workspace {
|
||||
cx,
|
||||
);
|
||||
cx.spawn_in(window, async move |_vh, cx| {
|
||||
let (multi_workspace_window, _) = task.await?;
|
||||
let OpenResult {
|
||||
window: multi_workspace_window,
|
||||
..
|
||||
} = task.await?;
|
||||
multi_workspace_window.update(cx, |multi_workspace, window, cx| {
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
workspace.update(cx, |workspace, cx| callback(workspace, window, cx))
|
||||
@@ -3102,7 +3107,7 @@ impl Workspace {
|
||||
paths: Vec<PathBuf>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
let window_handle = 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();
|
||||
@@ -3118,19 +3123,20 @@ impl Workspace {
|
||||
let app_state = self.app_state.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
cx.update(|cx| {
|
||||
open_paths(
|
||||
&paths,
|
||||
app_state,
|
||||
OpenOptions {
|
||||
replace_window: window_to_replace,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
let OpenResult { workspace, .. } = cx
|
||||
.update(|cx| {
|
||||
open_paths(
|
||||
&paths,
|
||||
app_state,
|
||||
OpenOptions {
|
||||
replace_window: window_to_replace,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok(workspace)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8210,7 +8216,7 @@ pub async fn restore_multiworkspace(
|
||||
cx.update(|cx| open_workspace_by_id(first.workspace_id, app_state.clone(), None, cx))
|
||||
.await?
|
||||
} else {
|
||||
let (window, _items) = cx
|
||||
let OpenResult { window, .. } = cx
|
||||
.update(|cx| {
|
||||
Workspace::new_local(
|
||||
first.paths.paths().to_vec(),
|
||||
@@ -8503,7 +8509,10 @@ pub fn join_channel(
|
||||
let mut active_window = requesting_window.or_else(|| activate_any_workspace_window(cx));
|
||||
if active_window.is_none() {
|
||||
// no open workspaces, make one to show the error in (blergh)
|
||||
let (window_handle, _) = cx
|
||||
let OpenResult {
|
||||
window: window_handle,
|
||||
..
|
||||
} = cx
|
||||
.update(|cx| {
|
||||
Workspace::new_local(
|
||||
vec![],
|
||||
@@ -8759,6 +8768,14 @@ pub struct OpenOptions {
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// The result of opening a workspace via [`open_paths`], [`Workspace::new_local`],
|
||||
/// or [`Workspace::open_workspace_for_paths`].
|
||||
pub struct OpenResult {
|
||||
pub window: WindowHandle<MultiWorkspace>,
|
||||
pub workspace: Entity<Workspace>,
|
||||
pub opened_items: Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
|
||||
}
|
||||
|
||||
/// Opens a workspace by its database ID, used for restoring empty workspaces with unsaved content.
|
||||
pub fn open_workspace_by_id(
|
||||
workspace_id: WorkspaceId,
|
||||
@@ -8878,12 +8895,7 @@ pub fn open_paths(
|
||||
app_state: Arc<AppState>,
|
||||
open_options: OpenOptions,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
anyhow::Result<(
|
||||
WindowHandle<MultiWorkspace>,
|
||||
Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<anyhow::Result<OpenResult>> {
|
||||
let abs_paths = abs_paths.to_vec();
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl_path = abs_paths
|
||||
@@ -8962,7 +8974,7 @@ pub fn open_paths(
|
||||
});
|
||||
});
|
||||
|
||||
Ok((existing, open_task))
|
||||
Ok(OpenResult { window: existing, workspace: target_workspace, opened_items: open_task })
|
||||
} else {
|
||||
let result = cx
|
||||
.update(move |cx| {
|
||||
@@ -8978,8 +8990,8 @@ pub fn open_paths(
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Ok((ref window_handle, _)) = result {
|
||||
window_handle
|
||||
if let Ok(ref result) = result {
|
||||
result.window
|
||||
.update(cx, |_, window, _cx| {
|
||||
window.activate_window();
|
||||
})
|
||||
@@ -8991,9 +9003,9 @@ pub fn open_paths(
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(util::paths::WslPath{distro, path}) = wsl_path
|
||||
&& let Ok((multi_workspace_window, _)) = &result
|
||||
&& let Ok(ref result) = result
|
||||
{
|
||||
multi_workspace_window
|
||||
result.window
|
||||
.update(cx, move |multi_workspace, _window, cx| {
|
||||
struct OpenInWsl;
|
||||
let workspace = multi_workspace.workspace().clone();
|
||||
@@ -9040,7 +9052,7 @@ pub fn open_new(
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |cx| {
|
||||
let (window, _opened_paths) = task.await?;
|
||||
let OpenResult { window, .. } = task.await?;
|
||||
window
|
||||
.update(cx, |_, window, _cx| {
|
||||
window.activate_window();
|
||||
|
||||
+11
-3
@@ -3442,7 +3442,11 @@ mod tests {
|
||||
PathBuf::from(path!("/root/.git/HEAD")),
|
||||
PathBuf::from(path!("/root/excluded_dir/ignored_subdir")),
|
||||
];
|
||||
let (opened_workspace, new_items) = cx
|
||||
let workspace::OpenResult {
|
||||
window: opened_workspace,
|
||||
opened_items: new_items,
|
||||
..
|
||||
} = cx
|
||||
.update(|cx| {
|
||||
workspace::open_paths(
|
||||
&paths_to_open,
|
||||
@@ -5866,7 +5870,9 @@ mod tests {
|
||||
//
|
||||
// Window A: workspace for dir1, workspace for dir2
|
||||
// Window B: workspace for dir3
|
||||
let (window_a, _) = cx
|
||||
let workspace::OpenResult {
|
||||
window: window_a, ..
|
||||
} = cx
|
||||
.update(|cx| {
|
||||
Workspace::new_local(
|
||||
vec![dir1.into()],
|
||||
@@ -5890,7 +5896,9 @@ mod tests {
|
||||
.expect("failed to open second workspace into window A");
|
||||
cx.run_until_parked();
|
||||
|
||||
let (window_b, _) = cx
|
||||
let workspace::OpenResult {
|
||||
window: window_b, ..
|
||||
} = cx
|
||||
.update(|cx| {
|
||||
Workspace::new_local(
|
||||
vec![dir3.into()],
|
||||
|
||||
@@ -29,7 +29,7 @@ use util::ResultExt;
|
||||
use util::paths::PathWithPosition;
|
||||
use workspace::PathList;
|
||||
use workspace::item::ItemHandle;
|
||||
use workspace::{AppState, MultiWorkspace, OpenOptions, SerializedWorkspaceLocation};
|
||||
use workspace::{AppState, MultiWorkspace, OpenOptions, OpenResult, SerializedWorkspaceLocation};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OpenRequest {
|
||||
@@ -345,7 +345,11 @@ pub async fn open_paths_with_positions(
|
||||
.map(|path_with_position| path_with_position.path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (multi_workspace, mut items) = cx
|
||||
let OpenResult {
|
||||
window: multi_workspace,
|
||||
opened_items: mut items,
|
||||
..
|
||||
} = cx
|
||||
.update(|cx| workspace::open_paths(&paths, app_state, open_options, cx))
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user