mirror of
https://github.com/zed-industries/zed.git
synced 2026-04-18 07:47:53 +00:00
Thread source workspace through worktree switches
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# Worktree-switch refactor follow-up hand-off
|
||||
|
||||
This document captures the work developed as a follow-up on top of Danilo Leal's original PR:
|
||||
|
||||
- Original PR: `#54183`
|
||||
- Original title: `Move the worktree picker to the title bar + make it always visible`
|
||||
- Original branch: `worktree-picker-title-bar`
|
||||
|
||||
The goal of this follow-up was **not** to change the visible worktree-picker product direction from the original PR. The goal was to finish the worktree-switch refactor so that switching from one workspace/worktree to another exposes the **source workspace directly**, and lets higher layers interpret that directly rather than relying on copied transition state or transient mailboxes.
|
||||
|
||||
## Architectural intent implemented in this follow-up
|
||||
|
||||
The intended model for worktree switching is now:
|
||||
|
||||
- the workspace/worktree switching path knows **which workspace it is leaving**
|
||||
- generic layers expose only `source_workspace: Option<WeakEntity<Workspace>>`
|
||||
- feature layers interpret that source workspace directly
|
||||
- `AgentPanel` initialization inspects the source workspace's `AgentPanel` directly
|
||||
- there is **no** copied draft mailbox/store and **no** generic transition payload blob
|
||||
|
||||
## What was explicitly avoided
|
||||
|
||||
The follow-up work intentionally avoided these designs:
|
||||
|
||||
- no `WorkspaceTransitionState` / `PanelTransitionState` / `AgentPanelTransitionState`
|
||||
- no generic payload object passed through workspace APIs
|
||||
- no `MultiWorkspace.pending_worktree_switch_text`
|
||||
- no `agent_ui` global draft handoff store
|
||||
- no side-channel source-stashes / destination-consumes mailbox design
|
||||
- no agent-specific transfer state in `MultiWorkspace`
|
||||
- no agent draft transport via `WorktreeCreationChanged`
|
||||
|
||||
## Core code changes made
|
||||
|
||||
### 1. Canonical activation API
|
||||
|
||||
`MultiWorkspace::activate(...)` was changed to the canonical form:
|
||||
|
||||
- `activate(workspace, source_workspace, window, cx)`
|
||||
|
||||
where:
|
||||
|
||||
- `source_workspace: Option<WeakEntity<Workspace>>`
|
||||
|
||||
This is the one canonical activation form. Ordinary activation paths now pass `None`. Worktree-switch activation paths pass `Some(source_workspace)`.
|
||||
|
||||
Related changes:
|
||||
|
||||
- `MultiWorkspaceEvent::ActiveWorkspaceChanged` now carries:
|
||||
- `source_workspace: Option<WeakEntity<Workspace>>`
|
||||
- subscribers that do not care about the source just match `ActiveWorkspaceChanged { .. }`
|
||||
- zed's agent panel setup path consumes the explicit source from that event
|
||||
|
||||
### 2. Generic worktree/workspace switching now threads source workspace explicitly
|
||||
|
||||
In `crates/git_ui/src/worktree_service.rs`:
|
||||
|
||||
- the source workspace is already available as the workspace we are switching away from
|
||||
- `open_worktree_workspace(...)` now passes that source workspace into the destination activation path explicitly
|
||||
- after finding or creating the destination workspace, activation is performed with:
|
||||
- `multi_workspace.activate(new_workspace.clone(), Some(source_workspace.clone()), window, cx)`
|
||||
|
||||
This preserves the intended primitive:
|
||||
|
||||
- "we are activating this destination workspace"
|
||||
- "here is the workspace we are coming from"
|
||||
|
||||
### 3. Agent panel setup path is now source-workspace aware
|
||||
|
||||
In `crates/zed/src/zed.rs`:
|
||||
|
||||
- `ensure_agent_panel_for_workspace(...)` now takes:
|
||||
- `source_workspace: Option<WeakEntity<Workspace>>`
|
||||
- after ensuring the `AgentPanel` is present, it calls into `AgentPanel` only if a source workspace was explicitly provided
|
||||
- the `MultiWorkspaceEvent::ActiveWorkspaceChanged { source_workspace }` subscription forwards that explicit source into the panel setup path
|
||||
|
||||
This keeps the source interpretation in the panel install/setup flow instead of in runtime draft-transport subscriptions.
|
||||
|
||||
### 4. AgentPanel now derives state directly from the source AgentPanel
|
||||
|
||||
In `crates/agent_ui/src/agent_panel.rs`:
|
||||
|
||||
Added source-aware initialization helpers:
|
||||
|
||||
- `destination_has_meaningful_state(...)`
|
||||
- `active_initial_content(...)`
|
||||
- `source_panel_initialization(...)`
|
||||
- `initialize_from_source_workspace_if_needed(...)`
|
||||
|
||||
The important behavior is:
|
||||
|
||||
- look up the source workspace directly
|
||||
- find its `AgentPanel` directly
|
||||
- read the source panel's active draft/editor state directly
|
||||
- initialize the destination panel only if the destination is effectively fresh/uninitialized
|
||||
|
||||
The destination overwrite policy is conservative:
|
||||
|
||||
- if destination panel already has meaningful initialized state, do nothing
|
||||
- if destination is fresh, initialize it from the source panel in one shot
|
||||
|
||||
The source-derived data comes from the source `AgentPanel` itself, not from a copied temporary store.
|
||||
|
||||
## Transient mechanisms that were removed
|
||||
|
||||
These mechanisms were removed from the on-disk diff:
|
||||
|
||||
- `PendingWorktreeDraftStore` in `agent_panel.rs`
|
||||
- `stash_pending_worktree_draft(...)`
|
||||
- `take_pending_worktree_draft(...)`
|
||||
- the `AgentPanel` `WorktreeCreationChanged` subscription used only for draft handoff
|
||||
- the `did_stash_worktree_draft` field
|
||||
- the temporary `Workspace.pending_source_workspace` approach that was briefly introduced during the refactor attempt
|
||||
- the separate `activate_with_source_workspace(...)` method, in favor of a single canonical `activate(...)`
|
||||
|
||||
There should now be no transient handoff store/mailbox in the codepath described above.
|
||||
|
||||
## Files materially changed in this follow-up
|
||||
|
||||
These files contain the main architectural changes:
|
||||
|
||||
- `crates/git_ui/src/worktree_service.rs`
|
||||
- `crates/workspace/src/multi_workspace.rs`
|
||||
- `crates/workspace/src/workspace.rs`
|
||||
- `crates/agent_ui/src/agent_panel.rs`
|
||||
- `crates/zed/src/zed.rs`
|
||||
|
||||
Other touched files are mostly call-site updates caused by the canonical `activate(...)` signature and related event shape changes:
|
||||
|
||||
- `crates/agent_ui/src/conversation_view.rs`
|
||||
- `crates/call/src/call_impl/mod.rs`
|
||||
- `crates/recent_projects/src/remote_connections.rs`
|
||||
- `crates/recent_projects/src/remote_servers.rs`
|
||||
- `crates/sidebar/src/sidebar.rs`
|
||||
- `crates/workspace/src/persistence.rs`
|
||||
- `crates/zed/src/visual_test_runner.rs`
|
||||
- plus some already-existing branch work in `git_ui` files
|
||||
|
||||
## Important current status / remaining work
|
||||
|
||||
The architectural refactor is in place, but this follow-up is **not fully mechanically finished**.
|
||||
|
||||
### Remaining known work
|
||||
|
||||
There are still remaining old 3-argument `activate(...)` test call sites that need to be updated to the canonical 4-argument form:
|
||||
|
||||
- `mw.activate(workspace, None, window, cx)`
|
||||
|
||||
The most obvious remaining cluster is in:
|
||||
|
||||
- `crates/sidebar/src/sidebar_tests.rs`
|
||||
|
||||
At the time of hand-off, that file had unsaved editor changes in the environment, so I intentionally did **not** force-save or overwrite it. That means the branch may still contain compile fallout from those unchanged old call sites.
|
||||
|
||||
There may also be a few other remaining mechanical call-site updates in tests/helpers that should be cleaned up with a repo-wide search for:
|
||||
|
||||
- `mw.activate(`
|
||||
- `multi_workspace.activate(`
|
||||
|
||||
and then converting remaining old-form calls to:
|
||||
|
||||
- `activate(workspace, None, window, cx)`
|
||||
|
||||
unless the call is a true worktree-switch case that should pass `Some(source_workspace)`.
|
||||
|
||||
### Diagnostics status
|
||||
|
||||
I ran targeted `cargo check` attempts during the refactor and used those diagnostics to fix the main architectural call sites.
|
||||
|
||||
The last known remaining failures were mechanical old-signature call sites after the canonical `activate(...)` change, not new architectural design issues.
|
||||
|
||||
I stopped short of further blind edits in files with unsaved buffer state.
|
||||
|
||||
## Suggested verification steps for the next person
|
||||
|
||||
1. Search for all remaining `activate(...)` call sites and ensure they use the canonical signature.
|
||||
2. Run a targeted `cargo check` again after finishing those mechanical updates.
|
||||
3. Verify these behaviors manually or with tests:
|
||||
- switching from workspace A to workspace B restores workspace-level dock/file/focus state from A
|
||||
- destination `AgentPanel` initializes from source `AgentPanel` when destination is fresh
|
||||
- destination `AgentPanel` does **not** overwrite meaningful existing destination state
|
||||
- no draft text is transported through any copied mailbox/store
|
||||
4. Consider adding or updating tests for:
|
||||
- source-aware activation event payload
|
||||
- conservative destination initialization behavior
|
||||
- reused destination workspace cases
|
||||
|
||||
## Summary of the intended final model
|
||||
|
||||
The target model for this follow-up is:
|
||||
|
||||
- workspace switching knows what workspace it is leaving
|
||||
- generic layers only expose that source workspace
|
||||
- feature layers interpret it themselves
|
||||
- `AgentPanel` initialization directly inspects the source `AgentPanel`
|
||||
- no copied draft mailbox/store exists anywhere
|
||||
|
||||
If additional cleanup is needed, it should stay within that architecture and avoid reintroducing any transient transfer state.
|
||||
@@ -59,8 +59,8 @@ use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, Corner,
|
||||
DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels,
|
||||
Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext,
|
||||
Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::LanguageModelRegistry;
|
||||
@@ -672,8 +672,6 @@ pub struct AgentPanel {
|
||||
_thread_view_subscription: Option<Subscription>,
|
||||
_active_thread_focus_subscription: Option<Subscription>,
|
||||
show_trust_workspace_message: bool,
|
||||
did_stash_worktree_draft: bool,
|
||||
pending_worktree_draft: Option<String>,
|
||||
_base_view_observation: Option<Subscription>,
|
||||
_draft_editor_observation: Option<Subscription>,
|
||||
}
|
||||
@@ -1038,73 +1036,11 @@ impl AgentPanel {
|
||||
_thread_view_subscription: None,
|
||||
_active_thread_focus_subscription: None,
|
||||
show_trust_workspace_message: false,
|
||||
did_stash_worktree_draft: false,
|
||||
pending_worktree_draft: None,
|
||||
new_user_onboarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)),
|
||||
_base_view_observation: None,
|
||||
_draft_editor_observation: None,
|
||||
};
|
||||
|
||||
// Subscribe to the workspace to detect worktree creation changes.
|
||||
// When creation starts (label becomes Some): this is the SOURCE panel.
|
||||
// → Stash draft text via the workspace helper and set a flag.
|
||||
// When creation ends (label becomes None): this is the DESTINATION panel
|
||||
// (the flag is false on fresh panels) → pick up the stashed text.
|
||||
if let Some(workspace_entity) = panel.workspace.upgrade() {
|
||||
cx.subscribe_in(
|
||||
&workspace_entity,
|
||||
window,
|
||||
|this, workspace, event: &workspace::Event, window, cx| {
|
||||
if !matches!(event, workspace::Event::WorktreeCreationChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
let is_creating = workspace
|
||||
.read(cx)
|
||||
.active_worktree_creation()
|
||||
.label
|
||||
.is_some();
|
||||
|
||||
if is_creating {
|
||||
let draft_prompt = this.active_thread_view(cx).and_then(|thread_view| {
|
||||
let text = thread_view.read(cx).message_editor.read(cx).text(cx);
|
||||
if text.is_empty() { None } else { Some(text) }
|
||||
});
|
||||
|
||||
let multi_workspace = workspace.read(cx).multi_workspace().cloned();
|
||||
if let Some(multi_workspace) = multi_workspace.and_then(|mw| mw.upgrade()) {
|
||||
multi_workspace.update(cx, |mw, _cx| {
|
||||
mw.pending_worktree_switch_text = draft_prompt;
|
||||
});
|
||||
}
|
||||
|
||||
this.did_stash_worktree_draft = true;
|
||||
} else if !this.did_stash_worktree_draft {
|
||||
let multi_workspace = workspace.read(cx).multi_workspace().cloned();
|
||||
let pending_text =
|
||||
multi_workspace.and_then(|mw| mw.upgrade()).and_then(|mw| {
|
||||
mw.update(cx, |mw, _cx| mw.pending_worktree_switch_text.take())
|
||||
});
|
||||
|
||||
if let Some(text) = pending_text {
|
||||
if let Some(thread_view) = this.active_thread_view(cx) {
|
||||
thread_view.update(cx, |thread_view, cx| {
|
||||
thread_view.message_editor.update(cx, |editor, cx| {
|
||||
editor.clear(window, cx);
|
||||
editor.insert_text(&text, window, cx);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.pending_worktree_draft = Some(text);
|
||||
this.ensure_thread_initialized(window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
// Initial sync of agent servers from extensions
|
||||
panel.sync_agent_servers_from_extensions(cx);
|
||||
panel
|
||||
@@ -2241,7 +2177,6 @@ impl AgentPanel {
|
||||
|this, server_view, window, cx| {
|
||||
this._thread_view_subscription =
|
||||
Self::subscribe_to_active_thread_view(&server_view, window, cx);
|
||||
this.apply_pending_worktree_draft(window, cx);
|
||||
cx.emit(AgentPanelEvent::ActiveViewChanged);
|
||||
this.serialize(cx);
|
||||
cx.notify();
|
||||
@@ -2784,25 +2719,104 @@ impl AgentPanel {
|
||||
if matches!(self.base_view, BaseView::Uninitialized) {
|
||||
self.activate_draft(false, window, cx);
|
||||
}
|
||||
|
||||
self.apply_pending_worktree_draft(window, cx);
|
||||
}
|
||||
|
||||
/// Tries to insert any existing draft prompt stashed in `pending_worktree_draft`
|
||||
/// into the active thread view's message editor.
|
||||
fn apply_pending_worktree_draft(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(text) = self.pending_worktree_draft.take() {
|
||||
if let Some(thread_view) = self.active_thread_view(cx) {
|
||||
thread_view.update(cx, |thread_view, cx| {
|
||||
thread_view.message_editor.update(cx, |editor, cx| {
|
||||
editor.clear(window, cx);
|
||||
editor.insert_text(&text, window, cx);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.pending_worktree_draft = Some(text);
|
||||
}
|
||||
fn destination_has_meaningful_state(&self, cx: &App) -> bool {
|
||||
if !matches!(self.base_view, BaseView::Uninitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.overlay_view.is_some() || !self.retained_threads.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.draft_thread.as_ref().is_some_and(|conversation_view| {
|
||||
conversation_view.read(cx).root_thread_view().is_some_and(|thread_view| {
|
||||
let thread_view = thread_view.read(cx);
|
||||
thread_view
|
||||
.thread
|
||||
.read(cx)
|
||||
.draft_prompt()
|
||||
.is_some_and(|draft| !draft.is_empty())
|
||||
|| !thread_view.message_editor.read(cx).text(cx).trim().is_empty()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn active_initial_content(&self, cx: &App) -> Option<AgentInitialContent> {
|
||||
self.active_thread_view(cx).and_then(|thread_view| {
|
||||
thread_view.read(cx).thread.read(cx).draft_prompt().map(|draft| {
|
||||
AgentInitialContent::ContentBlock {
|
||||
blocks: draft.to_vec(),
|
||||
auto_submit: false,
|
||||
}
|
||||
}).filter(|initial_content| match initial_content {
|
||||
AgentInitialContent::ContentBlock { blocks, .. } => !blocks.is_empty(),
|
||||
_ => true,
|
||||
}).or_else(|| {
|
||||
let text = thread_view.read(cx).message_editor.read(cx).text(cx);
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(AgentInitialContent::ContentBlock {
|
||||
blocks: vec![acp::ContentBlock::Text(acp::TextContent::new(text))],
|
||||
auto_submit: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn source_panel_initialization(
|
||||
source_workspace: &WeakEntity<Workspace>,
|
||||
cx: &App,
|
||||
) -> Option<(Agent, AgentInitialContent)> {
|
||||
let source_workspace = source_workspace.upgrade()?;
|
||||
let source_panel = source_workspace.read(cx).panel::<AgentPanel>(cx)?;
|
||||
let source_panel = source_panel.read(cx);
|
||||
let initial_content = source_panel.active_initial_content(cx)?;
|
||||
let agent = if source_panel.project.read(cx).is_via_collab() {
|
||||
Agent::NativeAgent
|
||||
} else {
|
||||
source_panel.selected_agent.clone()
|
||||
};
|
||||
Some((agent, initial_content))
|
||||
}
|
||||
|
||||
pub fn initialize_from_source_workspace_if_needed(
|
||||
&mut self,
|
||||
source_workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
if self.destination_has_meaningful_state(cx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some((agent, initial_content)) = Self::source_panel_initialization(&source_workspace, cx)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let agent = if self.project.read(cx).is_via_collab() {
|
||||
Agent::NativeAgent
|
||||
} else {
|
||||
agent
|
||||
};
|
||||
let thread = self.create_agent_thread(
|
||||
agent,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(initial_content),
|
||||
"agent_panel",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.draft_thread = Some(thread.conversation_view.clone());
|
||||
self.observe_draft_editor(&thread.conversation_view, cx);
|
||||
self.set_base_view(thread.into(), false, window, cx);
|
||||
true
|
||||
}
|
||||
|
||||
fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
|
||||
|
||||
@@ -2560,7 +2560,12 @@ impl ConversationView {
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
if let Some(workspace) = workspace_handle.upgrade() {
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(
|
||||
workspace.clone(),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.reveal_panel::<AgentPanel>(window, cx);
|
||||
if let Some(panel) =
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
|
||||
&cx.entity(),
|
||||
window,
|
||||
move |multi_workspace, _, event: &MultiWorkspaceEvent, window, cx| {
|
||||
if !matches!(event, MultiWorkspaceEvent::ActiveWorkspaceChanged)
|
||||
if !matches!(event, MultiWorkspaceEvent::ActiveWorkspaceChanged { .. })
|
||||
&& window.is_window_active()
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -70,17 +70,25 @@ pub fn init(cx: &mut App) {
|
||||
repository_selector::register(workspace);
|
||||
git_picker::register(workspace);
|
||||
|
||||
workspace.register_action(worktree_service::handle_create_worktree);
|
||||
workspace.register_action(worktree_service::handle_switch_worktree);
|
||||
workspace.register_action(|workspace, action: &zed_actions::CreateWorktree, window, cx| {
|
||||
worktree_service::handle_create_worktree(workspace, action, window, None, cx);
|
||||
});
|
||||
workspace.register_action(|workspace, action: &zed_actions::SwitchWorktree, window, cx| {
|
||||
worktree_service::handle_switch_worktree(workspace, action, window, None, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &zed_actions::git::Worktree, window, cx| {
|
||||
let focused_dock = workspace.focused_dock_position(window, cx);
|
||||
workspace.set_pre_picker_focused_dock(focused_dock);
|
||||
|
||||
let project = workspace.project().clone();
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
worktree_picker::WorktreePicker::new_modal(project, workspace_handle, window, cx)
|
||||
worktree_picker::WorktreePicker::new_modal(
|
||||
project,
|
||||
workspace_handle,
|
||||
focused_dock,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@ use ui::{
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::paths::PathExt;
|
||||
use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
|
||||
use workspace::{
|
||||
ModalView, MultiWorkspace, Workspace, dock::DockPosition,
|
||||
notifications::DetachAndPromptErr,
|
||||
};
|
||||
|
||||
use crate::git_panel::show_error_toast;
|
||||
use zed_actions::{
|
||||
@@ -41,27 +44,26 @@ impl WorktreePicker {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
if let Some(workspace) = workspace.upgrade() {
|
||||
let focused_dock = workspace.read(cx).focused_dock_position(window, cx);
|
||||
workspace.update(cx, |workspace, _cx| {
|
||||
workspace.set_pre_picker_focused_dock(focused_dock);
|
||||
});
|
||||
}
|
||||
Self::new_inner(project, workspace, false, window, cx)
|
||||
let focused_dock = workspace
|
||||
.upgrade()
|
||||
.and_then(|workspace| workspace.read(cx).focused_dock_position(window, cx));
|
||||
Self::new_inner(project, workspace, focused_dock, false, window, cx)
|
||||
}
|
||||
|
||||
pub fn new_modal(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focused_dock: Option<DockPosition>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new_inner(project, workspace, true, window, cx)
|
||||
Self::new_inner(project, workspace, focused_dock, true, window, cx)
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focused_dock: Option<DockPosition>,
|
||||
show_footer: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -104,6 +106,7 @@ impl WorktreePicker {
|
||||
selected_index: 0,
|
||||
project,
|
||||
workspace,
|
||||
focused_dock,
|
||||
current_branch_name,
|
||||
default_branch_name: None,
|
||||
has_multiple_repositories,
|
||||
@@ -255,6 +258,7 @@ pub struct WorktreePickerDelegate {
|
||||
selected_index: usize,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focused_dock: Option<DockPosition>,
|
||||
current_branch_name: Option<String>,
|
||||
default_branch_name: Option<String>,
|
||||
has_multiple_repositories: bool,
|
||||
@@ -558,26 +562,40 @@ impl PickerDelegate for WorktreePickerDelegate {
|
||||
match entry {
|
||||
WorktreeEntry::Separator => return,
|
||||
WorktreeEntry::CreateFromCurrentBranch => {
|
||||
window.dispatch_action(
|
||||
Box::new(CreateWorktree {
|
||||
worktree_name: None,
|
||||
branch_target: NewWorktreeBranchTarget::CurrentBranch,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
crate::worktree_service::handle_create_worktree(
|
||||
workspace,
|
||||
&CreateWorktree {
|
||||
worktree_name: None,
|
||||
branch_target: NewWorktreeBranchTarget::CurrentBranch,
|
||||
},
|
||||
window,
|
||||
self.focused_dock,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
WorktreeEntry::CreateFromDefaultBranch {
|
||||
default_branch_name,
|
||||
} => {
|
||||
window.dispatch_action(
|
||||
Box::new(CreateWorktree {
|
||||
worktree_name: None,
|
||||
branch_target: NewWorktreeBranchTarget::ExistingBranch {
|
||||
name: default_branch_name.clone(),
|
||||
},
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
crate::worktree_service::handle_create_worktree(
|
||||
workspace,
|
||||
&CreateWorktree {
|
||||
worktree_name: None,
|
||||
branch_target: NewWorktreeBranchTarget::ExistingBranch {
|
||||
name: default_branch_name.clone(),
|
||||
},
|
||||
},
|
||||
window,
|
||||
self.focused_dock,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
WorktreeEntry::Worktree { worktree, .. } => {
|
||||
let is_current = self.project_worktree_paths.contains(&worktree.path);
|
||||
@@ -596,13 +614,20 @@ impl PickerDelegate for WorktreePickerDelegate {
|
||||
.iter()
|
||||
.find(|wt| wt.is_main)
|
||||
.map(|wt| wt.path.as_path());
|
||||
window.dispatch_action(
|
||||
Box::new(SwitchWorktree {
|
||||
path: worktree.path.clone(),
|
||||
display_name: worktree.directory_name(main_worktree_path),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
crate::worktree_service::handle_switch_worktree(
|
||||
workspace,
|
||||
&SwitchWorktree {
|
||||
path: worktree.path.clone(),
|
||||
display_name: worktree.directory_name(main_worktree_path),
|
||||
},
|
||||
window,
|
||||
self.focused_dock,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,13 +642,20 @@ impl PickerDelegate for WorktreePickerDelegate {
|
||||
},
|
||||
None => NewWorktreeBranchTarget::CurrentBranch,
|
||||
};
|
||||
window.dispatch_action(
|
||||
Box::new(CreateWorktree {
|
||||
worktree_name: Some(name.clone()),
|
||||
branch_target,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
crate::worktree_service::handle_create_worktree(
|
||||
workspace,
|
||||
&CreateWorktree {
|
||||
worktree_name: Some(name.clone()),
|
||||
branch_target,
|
||||
},
|
||||
window,
|
||||
self.focused_dock,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
WorktreeEntry::CreateNamed {
|
||||
disabled_reason: Some(_),
|
||||
@@ -1095,6 +1127,7 @@ pub async fn open_remote_worktree(
|
||||
app_state,
|
||||
new_window,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -11,7 +11,9 @@ use project::project_settings::ProjectSettings;
|
||||
use project::trusted_worktrees::{PathTrust, TrustedWorktrees};
|
||||
use remote::RemoteConnectionOptions;
|
||||
use settings::Settings;
|
||||
use workspace::{MultiWorkspace, OpenMode, PreviousWorkspaceState, Workspace};
|
||||
use workspace::{
|
||||
MultiWorkspace, OpenMode, PreviousWorkspaceState, Workspace, dock::DockPosition,
|
||||
};
|
||||
use zed_actions::NewWorktreeBranchTarget;
|
||||
|
||||
use util::ResultExt as _;
|
||||
@@ -385,6 +387,7 @@ pub fn handle_create_worktree(
|
||||
workspace: &mut Workspace,
|
||||
action: &zed_actions::CreateWorktree,
|
||||
window: &mut gpui::Window,
|
||||
fallback_focused_dock: Option<DockPosition>,
|
||||
cx: &mut gpui::Context<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
@@ -403,7 +406,8 @@ pub fn handle_create_worktree(
|
||||
return;
|
||||
}
|
||||
|
||||
let previous_state = workspace.capture_state_for_worktree_switch(window, cx);
|
||||
let previous_state =
|
||||
workspace.capture_state_for_worktree_switch(window, fallback_focused_dock, cx);
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let window_handle = window.window_handle().downcast::<MultiWorkspace>();
|
||||
let remote_connection_options = project.read(cx).remote_connection_options(cx);
|
||||
@@ -484,6 +488,7 @@ pub fn handle_switch_worktree(
|
||||
workspace: &mut Workspace,
|
||||
action: &zed_actions::SwitchWorktree,
|
||||
window: &mut gpui::Window,
|
||||
fallback_focused_dock: Option<DockPosition>,
|
||||
cx: &mut gpui::Context<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
@@ -502,7 +507,8 @@ pub fn handle_switch_worktree(
|
||||
return;
|
||||
}
|
||||
|
||||
let previous_state = workspace.capture_state_for_worktree_switch(window, cx);
|
||||
let previous_state =
|
||||
workspace.capture_state_for_worktree_switch(window, fallback_focused_dock, cx);
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let window_handle = window.window_handle().downcast::<MultiWorkspace>();
|
||||
let remote_connection_options = project.read(cx).remote_connection_options(cx);
|
||||
@@ -719,7 +725,7 @@ async fn open_worktree_workspace(
|
||||
},
|
||||
);
|
||||
|
||||
let task = multi_workspace.find_or_create_workspace(
|
||||
let task = multi_workspace.find_or_create_workspace_with_source_workspace(
|
||||
path_list,
|
||||
remote_connection_options,
|
||||
None,
|
||||
@@ -734,6 +740,7 @@ async fn open_worktree_workspace(
|
||||
&[],
|
||||
Some(init),
|
||||
OpenMode::Add,
|
||||
Some(workspace.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -865,20 +872,17 @@ async fn open_worktree_workspace(
|
||||
.ok();
|
||||
|
||||
window_handle.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.activate(new_workspace.clone(), window, cx);
|
||||
multi_workspace.activate(
|
||||
new_workspace.clone(),
|
||||
Some(workspace.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
new_workspace.update(cx, |workspace, cx| {
|
||||
workspace.run_create_worktree_tasks(window, cx);
|
||||
});
|
||||
|
||||
// Signal completion on the NEW workspace so its agent panel
|
||||
// subscriber fires and picks up stashed draft text.
|
||||
// This must be a separate update so the event is delivered
|
||||
// before focus restoration triggers ensure_thread_initialized.
|
||||
new_workspace.update(cx, |workspace, cx| {
|
||||
workspace.set_active_worktree_creation(None, false, cx);
|
||||
});
|
||||
|
||||
if let Some(dock_position) = focused_dock {
|
||||
new_workspace.update(cx, |workspace, cx| {
|
||||
let dock = workspace.dock_at_position(dock_position);
|
||||
|
||||
@@ -160,7 +160,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(), window, cx);
|
||||
multi_workspace.activate(existing_workspace.clone(), None, window, cx);
|
||||
existing_workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_paths(
|
||||
resolved_paths,
|
||||
|
||||
@@ -505,7 +505,7 @@ impl ProjectPicker {
|
||||
}?;
|
||||
|
||||
let items = open_remote_project_with_existing_connection(
|
||||
connection, project, paths, app_state, window, None, cx,
|
||||
connection, project, paths, app_state, window, None, None, cx,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
@@ -402,7 +402,7 @@ impl Sidebar {
|
||||
&multi_workspace,
|
||||
window,
|
||||
|this, _multi_workspace, event: &MultiWorkspaceEvent, window, cx| match event {
|
||||
MultiWorkspaceEvent::ActiveWorkspaceChanged => {
|
||||
MultiWorkspaceEvent::ActiveWorkspaceChanged { .. } => {
|
||||
this.sync_active_entry_from_active_workspace(cx);
|
||||
this.replace_archived_panel_thread(window, cx);
|
||||
this.update_entries(cx);
|
||||
@@ -2224,7 +2224,7 @@ impl Sidebar {
|
||||
}
|
||||
|
||||
multi_workspace.update(cx, |multi_workspace, cx| {
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(workspace.clone(), None, window, cx);
|
||||
if retain {
|
||||
multi_workspace.retain_active_workspace(cx);
|
||||
}
|
||||
@@ -2264,7 +2264,7 @@ impl Sidebar {
|
||||
let activated = target_window
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(workspace.clone(), None, window, cx);
|
||||
Self::load_agent_thread_in_workspace(&workspace, &metadata, true, window, cx);
|
||||
})
|
||||
.log_err()
|
||||
@@ -3286,7 +3286,7 @@ impl Sidebar {
|
||||
) {
|
||||
if let Some(multi_workspace) = self.multi_workspace.upgrade() {
|
||||
multi_workspace.update(cx, |mw, cx| {
|
||||
mw.activate(workspace.clone(), window, cx);
|
||||
mw.activate(workspace.clone(), None, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3490,7 +3490,7 @@ impl Sidebar {
|
||||
} => {
|
||||
if let Some(mw) = weak_multi_workspace.upgrade() {
|
||||
mw.update(cx, |mw, cx| {
|
||||
mw.activate(workspace.clone(), window, cx);
|
||||
mw.activate(workspace.clone(), None, window, cx);
|
||||
});
|
||||
}
|
||||
this.active_entry = Some(ActiveEntry {
|
||||
@@ -3509,7 +3509,7 @@ impl Sidebar {
|
||||
} => {
|
||||
if let Some(mw) = weak_multi_workspace.upgrade() {
|
||||
mw.update(cx, |mw, cx| {
|
||||
mw.activate(workspace.clone(), window, cx);
|
||||
mw.activate(workspace.clone(), None, window, cx);
|
||||
mw.retain_active_workspace(cx);
|
||||
});
|
||||
}
|
||||
@@ -3527,7 +3527,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(), window, cx);
|
||||
mw.activate(original_ws.clone(), None, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3584,7 +3584,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(), window, cx);
|
||||
mw.activate(workspace.clone(), None, window, cx);
|
||||
});
|
||||
}
|
||||
self.active_entry = Some(ActiveEntry {
|
||||
@@ -3826,7 +3826,7 @@ impl Sidebar {
|
||||
};
|
||||
|
||||
multi_workspace.update(cx, |multi_workspace, cx| {
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(workspace.clone(), None, window, cx);
|
||||
});
|
||||
|
||||
let draft_id = workspace.update(cx, |workspace, cx| {
|
||||
@@ -3953,7 +3953,7 @@ impl Sidebar {
|
||||
.workspace_for_paths(key.path_list(), key.host().as_ref(), cx)
|
||||
}) {
|
||||
multi_workspace.update(cx, |multi_workspace, cx| {
|
||||
multi_workspace.activate(workspace, window, cx);
|
||||
multi_workspace.activate(workspace, None, window, cx);
|
||||
multi_workspace.retain_active_workspace(cx);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -103,7 +103,9 @@ pub fn sidebar_side_context_menu(
|
||||
}
|
||||
|
||||
pub enum MultiWorkspaceEvent {
|
||||
ActiveWorkspaceChanged,
|
||||
ActiveWorkspaceChanged {
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
},
|
||||
WorkspaceAdded(Entity<Workspace>),
|
||||
WorkspaceRemoved(EntityId),
|
||||
ProjectGroupsChanged,
|
||||
@@ -294,10 +296,6 @@ pub struct MultiWorkspace {
|
||||
_serialize_task: Option<Task<()>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
previous_focus_handle: Option<FocusHandle>,
|
||||
/// Holds unsent agent editor text during a worktree switch.
|
||||
/// The agent panel in the source workspace stashes its draft text here,
|
||||
/// and the agent panel in the destination workspace picks it up.
|
||||
pub pending_worktree_switch_text: Option<String>,
|
||||
}
|
||||
|
||||
impl EventEmitter<MultiWorkspaceEvent> for MultiWorkspace {}
|
||||
@@ -356,7 +354,6 @@ impl MultiWorkspace {
|
||||
settings_subscription,
|
||||
],
|
||||
previous_focus_handle: None,
|
||||
pending_worktree_switch_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +580,7 @@ impl MultiWorkspace {
|
||||
|
||||
cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| {
|
||||
if let WorkspaceEvent::Activate = event {
|
||||
this.activate(workspace.clone(), window, cx);
|
||||
this.activate(workspace.clone(), None, window, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -1023,19 +1020,52 @@ impl MultiWorkspace {
|
||||
open_mode: OpenMode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
self.find_or_create_workspace_with_source_workspace(
|
||||
paths,
|
||||
host,
|
||||
provisional_project_group_key,
|
||||
connect_remote,
|
||||
excluding,
|
||||
init,
|
||||
open_mode,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn find_or_create_workspace_with_source_workspace(
|
||||
&mut self,
|
||||
paths: PathList,
|
||||
host: Option<RemoteConnectionOptions>,
|
||||
provisional_project_group_key: Option<ProjectGroupKey>,
|
||||
connect_remote: impl FnOnce(
|
||||
RemoteConnectionOptions,
|
||||
&mut Window,
|
||||
&mut Context<Self>,
|
||||
) -> Task<Result<Option<Entity<remote::RemoteClient>>>>
|
||||
+ 'static,
|
||||
excluding: &[Entity<Workspace>],
|
||||
init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
|
||||
open_mode: OpenMode,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
if let Some(workspace) = self.workspace_for_paths(&paths, host.as_ref(), cx) {
|
||||
self.activate(workspace.clone(), window, cx);
|
||||
self.activate(workspace.clone(), source_workspace, window, cx);
|
||||
return Task::ready(Ok(workspace));
|
||||
}
|
||||
|
||||
let Some(connection_options) = host else {
|
||||
return self.find_or_create_local_workspace(
|
||||
return self.find_or_create_local_workspace_with_source_workspace(
|
||||
paths,
|
||||
provisional_project_group_key,
|
||||
excluding,
|
||||
init,
|
||||
open_mode,
|
||||
source_workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -1074,6 +1104,7 @@ impl MultiWorkspace {
|
||||
app_state,
|
||||
window_handle,
|
||||
provisional_project_group_key,
|
||||
source_workspace,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
@@ -1102,10 +1133,33 @@ impl MultiWorkspace {
|
||||
open_mode: OpenMode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
self.find_or_create_local_workspace_with_source_workspace(
|
||||
path_list,
|
||||
project_group,
|
||||
excluding,
|
||||
init,
|
||||
open_mode,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn find_or_create_local_workspace_with_source_workspace(
|
||||
&mut self,
|
||||
path_list: PathList,
|
||||
project_group: Option<ProjectGroupKey>,
|
||||
excluding: &[Entity<Workspace>],
|
||||
init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
|
||||
open_mode: OpenMode,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
if let Some(workspace) = self.workspace_for_paths_excluding(&path_list, None, excluding, cx)
|
||||
{
|
||||
self.activate(workspace.clone(), window, cx);
|
||||
self.activate(workspace.clone(), source_workspace, window, cx);
|
||||
return Task::ready(Ok(workspace));
|
||||
}
|
||||
|
||||
@@ -1150,7 +1204,12 @@ impl MultiWorkspace {
|
||||
cx,
|
||||
)
|
||||
.inspect(|workspace| {
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(
|
||||
workspace.clone(),
|
||||
source_workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
@@ -1213,6 +1272,7 @@ impl MultiWorkspace {
|
||||
pub fn activate(
|
||||
&mut self,
|
||||
workspace: Entity<Workspace>,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -1245,7 +1305,7 @@ impl MultiWorkspace {
|
||||
self.detach_workspace(&old_active_workspace, cx);
|
||||
}
|
||||
|
||||
cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
|
||||
cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged { source_workspace });
|
||||
self.serialize(cx);
|
||||
self.focus_active_workspace(window, cx);
|
||||
cx.notify();
|
||||
@@ -1675,12 +1735,12 @@ impl MultiWorkspace {
|
||||
!workspaces.contains(&new_active),
|
||||
"fallback workspace must not be one of the workspaces being removed"
|
||||
);
|
||||
this.activate(new_active, window, cx);
|
||||
this.activate(new_active, None, window, cx);
|
||||
})?;
|
||||
} else {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
if *this.workspace() != original_active {
|
||||
this.activate(original_active, window, cx);
|
||||
this.activate(original_active, None, window, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -2663,7 +2663,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(), window, cx);
|
||||
mw.activate(workspace.clone(), None, window, cx);
|
||||
workspace
|
||||
});
|
||||
|
||||
@@ -5066,7 +5066,7 @@ mod tests {
|
||||
|
||||
// Activate workspace B so removing its group exercises the fallback.
|
||||
multi_workspace.update_in(cx, |mw, window, cx| {
|
||||
mw.activate(workspace_b.clone(), window, cx);
|
||||
mw.activate(workspace_b.clone(), None, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -5095,7 +5095,7 @@ mod tests {
|
||||
let workspace_a =
|
||||
multi_workspace.read_with(cx, |mw, _cx| mw.workspaces().next().unwrap().clone());
|
||||
multi_workspace.update_in(cx, |mw, window, cx| {
|
||||
mw.activate(workspace_a.clone(), window, cx);
|
||||
mw.activate(workspace_a.clone(), None, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -5171,7 +5171,7 @@ mod tests {
|
||||
|
||||
// Activate workspace_a so removing it triggers the fallback path.
|
||||
multi_workspace.update_in(cx, |mw, window, cx| {
|
||||
mw.activate(workspace_a.clone(), window, cx);
|
||||
mw.activate(workspace_a.clone(), None, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
|
||||
@@ -1398,7 +1398,6 @@ pub struct Workspace {
|
||||
sidebar_focus_handle: Option<FocusHandle>,
|
||||
multi_workspace: Option<WeakEntity<MultiWorkspace>>,
|
||||
active_worktree_creation: ActiveWorktreeCreation,
|
||||
pre_picker_focused_dock: Option<DockPosition>,
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for Workspace {}
|
||||
@@ -1828,7 +1827,6 @@ impl Workspace {
|
||||
sidebar_focus_handle: None,
|
||||
multi_workspace,
|
||||
active_worktree_creation: ActiveWorktreeCreation::default(),
|
||||
pre_picker_focused_dock: None,
|
||||
open_in_dev_container: false,
|
||||
_dev_container_task: None,
|
||||
}
|
||||
@@ -1971,7 +1969,7 @@ impl Workspace {
|
||||
});
|
||||
match open_mode {
|
||||
OpenMode::Activate => {
|
||||
multi_workspace.activate(workspace.clone(), window, cx);
|
||||
multi_workspace.activate(workspace.clone(), None, window, cx);
|
||||
}
|
||||
OpenMode::Add => {
|
||||
multi_workspace.add(workspace.clone(), &*window, cx);
|
||||
@@ -2233,19 +2231,12 @@ impl Workspace {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn pre_picker_focused_dock(&self) -> Option<DockPosition> {
|
||||
self.pre_picker_focused_dock
|
||||
}
|
||||
|
||||
pub fn set_pre_picker_focused_dock(&mut self, position: Option<DockPosition>) {
|
||||
self.pre_picker_focused_dock = position;
|
||||
}
|
||||
|
||||
/// Captures the current workspace state for restoring after a worktree switch.
|
||||
/// This includes dock layout, open file paths, and the active file path.
|
||||
pub fn capture_state_for_worktree_switch(
|
||||
&self,
|
||||
window: &Window,
|
||||
fallback_focused_dock: Option<DockPosition>,
|
||||
cx: &App,
|
||||
) -> PreviousWorkspaceState {
|
||||
let dock_structure = self.capture_dock_state(window, cx);
|
||||
@@ -2265,7 +2256,7 @@ impl Workspace {
|
||||
dock.read(cx).is_open() && dock.focus_handle(cx).contains_focused(window, cx)
|
||||
})
|
||||
.map(|(position, _)| position)
|
||||
.or_else(|| self.pre_picker_focused_dock);
|
||||
.or(fallback_focused_dock);
|
||||
|
||||
PreviousWorkspaceState {
|
||||
dock_structure,
|
||||
@@ -9724,7 +9715,7 @@ pub fn open_paths(
|
||||
let open_task = existing
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
window.activate_window();
|
||||
multi_workspace.activate(target_workspace.clone(), window, cx);
|
||||
multi_workspace.activate(target_workspace.clone(), None, window, cx);
|
||||
target_workspace.update(cx, |workspace, cx| {
|
||||
if open_in_dev_container {
|
||||
workspace.set_open_in_dev_container(true);
|
||||
@@ -9950,6 +9941,7 @@ pub fn open_remote_project_with_new_connection(
|
||||
app_state,
|
||||
window,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
@@ -9963,6 +9955,7 @@ pub fn open_remote_project_with_existing_connection(
|
||||
app_state: Arc<AppState>,
|
||||
window: WindowHandle<MultiWorkspace>,
|
||||
provisional_project_group_key: Option<ProjectGroupKey>,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
|
||||
cx.spawn(async move |cx| {
|
||||
@@ -9977,6 +9970,7 @@ pub fn open_remote_project_with_existing_connection(
|
||||
app_state,
|
||||
window,
|
||||
provisional_project_group_key,
|
||||
source_workspace,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
@@ -9991,6 +9985,7 @@ async fn open_remote_project_inner(
|
||||
app_state: Arc<AppState>,
|
||||
window: WindowHandle<MultiWorkspace>,
|
||||
provisional_project_group_key: Option<ProjectGroupKey>,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
||||
let db = cx.update(|cx| WorkspaceDb::global(cx));
|
||||
@@ -10056,7 +10051,7 @@ async fn open_remote_project_inner(
|
||||
if let Some(project_group_key) = provisional_project_group_key.clone() {
|
||||
multi_workspace.retain_workspace(new_workspace.clone(), project_group_key, cx);
|
||||
}
|
||||
multi_workspace.activate(new_workspace.clone(), window, cx);
|
||||
multi_workspace.activate(new_workspace.clone(), source_workspace, window, cx);
|
||||
new_workspace
|
||||
})?;
|
||||
|
||||
@@ -10143,7 +10138,7 @@ pub fn join_in_room_project(
|
||||
{
|
||||
existing_window
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.activate(target_workspace, window, cx);
|
||||
multi_workspace.activate(target_workspace, None, window, cx);
|
||||
})
|
||||
.ok();
|
||||
existing_window
|
||||
@@ -11083,7 +11078,7 @@ mod tests {
|
||||
// Activate workspace A
|
||||
multi_workspace_handle
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.activate(workspace_a.clone(), window, cx);
|
||||
mw.activate(workspace_a.clone(), None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -11168,7 +11163,7 @@ mod tests {
|
||||
// Activate workspace A.
|
||||
multi_workspace_handle
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.activate(workspace_a.clone(), window, cx);
|
||||
mw.activate(workspace_a.clone(), None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -11220,7 +11215,7 @@ mod tests {
|
||||
let remove_task = multi_workspace_handle
|
||||
.update(cx, |mw, window, cx| {
|
||||
// First switch back to A.
|
||||
mw.activate(workspace_a.clone(), window, cx);
|
||||
mw.activate(workspace_a.clone(), None, window, cx);
|
||||
mw.remove([workspace_b.clone()], |_, _, _| unreachable!(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -2605,7 +2605,7 @@ fn run_multi_workspace_sidebar_visual_tests(
|
||||
});
|
||||
cx.new(|cx| {
|
||||
let mut multi_workspace = MultiWorkspace::new(workspace1, window, cx);
|
||||
multi_workspace.activate(workspace2, window, cx);
|
||||
multi_workspace.activate(workspace2, None, window, cx);
|
||||
multi_workspace
|
||||
})
|
||||
},
|
||||
@@ -2657,7 +2657,7 @@ fn run_multi_workspace_sidebar_visual_tests(
|
||||
multi_workspace_window
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
let workspace = multi_workspace.workspaces().next().unwrap().clone();
|
||||
multi_workspace.activate(workspace, window, cx);
|
||||
multi_workspace.activate(workspace, None, window, cx);
|
||||
})
|
||||
.context("Failed to activate workspace 1")?;
|
||||
|
||||
@@ -3393,7 +3393,7 @@ fn open_sidebar_test_window(
|
||||
let ws = cx.new(|cx| {
|
||||
Workspace::new(None, project, app_state.clone(), window, cx)
|
||||
});
|
||||
mw.activate(ws, window, cx);
|
||||
mw.activate(ws, None, window, cx);
|
||||
}
|
||||
mw
|
||||
})
|
||||
|
||||
+63
-15
@@ -423,6 +423,31 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut App) {
|
||||
|
||||
let window_handle = window.window_handle();
|
||||
let multi_workspace_handle = cx.entity();
|
||||
cx.subscribe_in(
|
||||
&multi_workspace_handle,
|
||||
window,
|
||||
|_, multi_workspace, event: &workspace::MultiWorkspaceEvent, window, cx| {
|
||||
let workspace::MultiWorkspaceEvent::ActiveWorkspaceChanged { source_workspace } =
|
||||
event
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let active_workspace = multi_workspace.read(cx).workspace().clone();
|
||||
let source_workspace = source_workspace.clone();
|
||||
active_workspace.update(cx, |workspace, cx| {
|
||||
ensure_agent_panel_for_workspace(
|
||||
workspace,
|
||||
source_workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.defer(move |cx| {
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
@@ -735,24 +760,47 @@ fn setup_or_teardown_ai_panel<P: Panel>(
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_agent_panel_for_workspace(
|
||||
workspace: &mut Workspace,
|
||||
source_workspace: Option<WeakEntity<Workspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let task = setup_or_teardown_ai_panel(workspace, window, cx, move |workspace, cx| {
|
||||
agent_ui::AgentPanel::load(workspace, cx)
|
||||
});
|
||||
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
task.await?;
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
if let Some(source_workspace) = source_workspace.clone()
|
||||
&& let Some(panel) = workspace.panel::<agent_ui::AgentPanel>(cx)
|
||||
{
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.initialize_from_source_workspace_if_needed(
|
||||
source_workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async fn initialize_agent_panel(
|
||||
workspace_handle: WeakEntity<Workspace>,
|
||||
mut cx: AsyncWindowContext,
|
||||
) -> anyhow::Result<()> {
|
||||
workspace_handle
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
setup_or_teardown_ai_panel(workspace, window, cx, move |workspace, cx| {
|
||||
agent_ui::AgentPanel::load(workspace, cx)
|
||||
})
|
||||
ensure_agent_panel_for_workspace(workspace, None, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
workspace_handle.update_in(&mut cx, |workspace, window, cx| {
|
||||
cx.observe_global_in::<SettingsStore>(window, move |workspace, window, cx| {
|
||||
setup_or_teardown_ai_panel(workspace, window, cx, move |workspace, cx| {
|
||||
agent_ui::AgentPanel::load(workspace, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
ensure_agent_panel_for_workspace(workspace, None, window, cx).detach_and_log_err(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -1558,7 +1606,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(), window, cx);
|
||||
multi_workspace.activate(workspace.clone(), None, window, cx);
|
||||
window.activate_window();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
|
||||
@@ -5673,10 +5721,10 @@ mod tests {
|
||||
|
||||
window
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.activate(workspace2.clone(), window, cx);
|
||||
multi_workspace.activate(workspace3.clone(), window, cx);
|
||||
multi_workspace.activate(workspace2.clone(), None, window, cx);
|
||||
multi_workspace.activate(workspace3.clone(), None, window, cx);
|
||||
// Switch back to workspace1 for test setup
|
||||
multi_workspace.activate(workspace1.clone(), window, cx);
|
||||
multi_workspace.activate(workspace1.clone(), None, window, cx);
|
||||
assert_eq!(multi_workspace.workspace(), &workspace1);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -5860,8 +5908,8 @@ mod tests {
|
||||
|
||||
window1
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.activate(workspace1_2.clone(), window, cx);
|
||||
multi_workspace.activate(workspace1_1.clone(), window, cx);
|
||||
multi_workspace.activate(workspace1_2.clone(), None, window, cx);
|
||||
multi_workspace.activate(workspace1_1.clone(), None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -6180,7 +6228,7 @@ mod tests {
|
||||
window_a
|
||||
.update(cx, |multi_workspace, window, cx| {
|
||||
let workspace = multi_workspace.workspaces().next().unwrap().clone();
|
||||
multi_workspace.activate(workspace, window, cx);
|
||||
multi_workspace.activate(workspace, None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -6388,7 +6436,7 @@ mod tests {
|
||||
})
|
||||
.expect("workspace_a should exist")
|
||||
.clone();
|
||||
mw.activate(workspace_a, window, cx);
|
||||
mw.activate(workspace_a, None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
Reference in New Issue
Block a user