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:
Mikayla Maki authored and GitHub committed 2026-03-12 15:34:09 -07:00
1 parent d11fdd2ebe
commit c90707e306
8 files changed
+399 -82

No files matched your search

+6 -11
View File
@@ -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
View File
@@ -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"
);
}
}
+5 -2
View File
@@ -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],
+14 -7
View File
@@ -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);
}
+2 -2
View File
@@ -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)
}
})
}
+50 -38
View File
@@ -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
View File
@@ -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()],
+6 -2
View File
@@ -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?;