diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 9fd829675dca1e8a1d7bc4301407a3336603e440..2646690003f238b2ab97809725244ed8344d4df6 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -44,9 +44,10 @@ use ui::{ use util::ResultExt as _; use util::path_list::{PathList, SerializedPathList}; use workspace::{ - AddFolderToProject, CloseWindow, FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, - Open, Sidebar as WorkspaceSidebar, SidebarSide, ToggleWorkspaceSidebar, Workspace, WorkspaceId, - sidebar_side_context_menu, + AddFolderToProject, CloseWindow, FocusWorkspaceSidebar, MoveWorkspaceToNewWindow, + MultiWorkspace, MultiWorkspaceEvent, NextProjectGroup, NextThread, Open, PreviousProjectGroup, + PreviousThread, ShowFewerThreads, ShowMoreThreads, Sidebar as WorkspaceSidebar, SidebarSide, + ToggleWorkspaceSidebar, Workspace, WorkspaceId, sidebar_side_context_menu, }; use zed_actions::OpenRecent; @@ -1611,9 +1612,7 @@ impl Sidebar { let multi_workspace = multi_workspace.clone(); menu.entry( "Move to New Window", - Some(Box::new( - zed_actions::agents_sidebar::MoveWorkspaceToNewWindow, - )), + Some(Box::new(MoveWorkspaceToNewWindow)), move |window, cx| { multi_workspace .update(cx, |multi_workspace, cx| { @@ -1948,7 +1947,7 @@ impl Sidebar { match &thread.workspace { ThreadEntryWorkspace::Open(workspace) => { let workspace = workspace.clone(); - self.activate_thread(metadata, &workspace, window, cx); + self.activate_thread(metadata, &workspace, false, window, cx); } ThreadEntryWorkspace::Closed(path_list) => { self.open_workspace_and_activate_thread( @@ -1967,13 +1966,10 @@ impl Sidebar { } => { let path_list = key.path_list().clone(); if *is_fully_expanded { - self.expanded_groups.remove(&path_list); + self.reset_thread_group_expansion(&path_list, cx); } else { - let current = self.expanded_groups.get(&path_list).copied().unwrap_or(0); - self.expanded_groups.insert(path_list, current + 1); + self.expand_thread_group(&path_list, cx); } - self.serialize(cx); - self.update_entries(cx); } ListEntry::DraftThread { .. } => { // Already active — nothing to do. @@ -2052,6 +2048,7 @@ impl Sidebar { &mut self, metadata: &ThreadMetadata, workspace: &Entity, + retain: bool, window: &mut Window, cx: &mut Context, ) { @@ -2070,6 +2067,9 @@ impl Sidebar { multi_workspace.update(cx, |multi_workspace, cx| { multi_workspace.activate(workspace.clone(), window, cx); + if retain { + multi_workspace.retain_active_workspace(cx); + } }); Self::load_agent_thread_in_workspace(workspace, metadata, true, window, cx); @@ -2121,6 +2121,7 @@ impl Sidebar { &mut self, metadata: ThreadMetadata, workspace: &Entity, + retain: bool, window: &mut Window, cx: &mut Context, ) { @@ -2128,7 +2129,7 @@ impl Sidebar { .find_workspace_in_current_window(cx, |candidate, _| candidate == workspace) .is_some() { - self.activate_thread_locally(&metadata, &workspace, window, cx); + self.activate_thread_locally(&metadata, &workspace, retain, window, cx); return; } @@ -2159,7 +2160,7 @@ impl Sidebar { cx.spawn_in(window, async move |this, cx| { let workspace = open_task.await?; this.update_in(cx, |this, window, cx| { - this.activate_thread(metadata, &workspace, window, cx); + this.activate_thread(metadata, &workspace, false, window, cx); })?; anyhow::Ok(()) }) @@ -2198,7 +2199,7 @@ impl Sidebar { if !metadata.folder_paths.paths().is_empty() { let path_list = metadata.folder_paths.clone(); if let Some(workspace) = self.find_current_workspace_for_path_list(&path_list, cx) { - self.activate_thread_locally(&metadata, &workspace, window, cx); + self.activate_thread_locally(&metadata, &workspace, false, window, cx); } else if let Some((target_window, workspace)) = self.find_open_workspace_for_path_list(&path_list, cx) { @@ -2215,7 +2216,7 @@ impl Sidebar { .map(|w| w.read(cx).workspace().clone()); if let Some(workspace) = active_workspace { - self.activate_thread_locally(&metadata, &workspace, window, cx); + self.activate_thread_locally(&metadata, &workspace, false, window, cx); } } @@ -2683,6 +2684,7 @@ impl Sidebar { if let Some(mw) = weak_multi_workspace.upgrade() { mw.update(cx, |mw, cx| { mw.activate(workspace.clone(), window, cx); + mw.retain_active_workspace(cx); }); } this.record_thread_access(&metadata.session_id); @@ -2896,7 +2898,7 @@ impl Sidebar { this.selection = None; match &thread_workspace { ThreadEntryWorkspace::Open(workspace) => { - this.activate_thread(metadata.clone(), workspace, window, cx); + this.activate_thread(metadata.clone(), workspace, false, window, cx); } ThreadEntryWorkspace::Closed(path_list) => { this.open_workspace_and_activate_thread( @@ -3007,13 +3009,10 @@ impl Sidebar { .on_click(cx.listener(move |this, _, _window, cx| { this.selection = None; if is_fully_expanded { - this.expanded_groups.remove(&path_list); + this.reset_thread_group_expansion(&path_list, cx); } else { - let current = this.expanded_groups.get(&path_list).copied().unwrap_or(0); - this.expanded_groups.insert(path_list.clone(), current + 1); + this.expand_thread_group(&path_list, cx); } - this.serialize(cx); - this.update_entries(cx); })) .into_any_element() } @@ -3079,6 +3078,242 @@ impl Sidebar { }); } + fn active_project_group_key(&self, cx: &App) -> Option { + let multi_workspace = self.multi_workspace.upgrade()?; + let mw = multi_workspace.read(cx); + Some(mw.workspace().read(cx).project_group_key(cx)) + } + + fn active_project_header_position(&self, cx: &App) -> Option { + let active_key = self.active_project_group_key(cx)?; + self.contents + .project_header_indices + .iter() + .position(|&entry_ix| { + matches!( + &self.contents.entries[entry_ix], + ListEntry::ProjectHeader { key, .. } if *key == active_key + ) + }) + } + + fn cycle_project_group_impl( + &mut self, + forward: bool, + window: &mut Window, + cx: &mut Context, + ) { + let Some(multi_workspace) = self.multi_workspace.upgrade() else { + return; + }; + + let header_count = self.contents.project_header_indices.len(); + if header_count == 0 { + return; + } + + let current_pos = self.active_project_header_position(cx); + + let next_pos = match current_pos { + Some(pos) => { + if forward { + (pos + 1) % header_count + } else { + (pos + header_count - 1) % header_count + } + } + None => 0, + }; + + let header_entry_ix = self.contents.project_header_indices[next_pos]; + let Some(ListEntry::ProjectHeader { key, .. }) = self.contents.entries.get(header_entry_ix) + else { + return; + }; + let path_list = key.path_list().clone(); + + // Uncollapse the target group so that threads become visible. + self.collapsed_groups.remove(&path_list); + + if let Some(workspace) = self.workspace_for_group(&path_list, cx) { + multi_workspace.update(cx, |multi_workspace, cx| { + multi_workspace.activate(workspace, window, cx); + multi_workspace.retain_active_workspace(cx); + }); + } else { + self.open_workspace_for_group(&path_list, window, cx); + } + } + + fn on_next_project_group( + &mut self, + _: &NextProjectGroup, + window: &mut Window, + cx: &mut Context, + ) { + self.cycle_project_group_impl(true, window, cx); + } + + fn on_previous_project_group( + &mut self, + _: &PreviousProjectGroup, + window: &mut Window, + cx: &mut Context, + ) { + self.cycle_project_group_impl(false, window, cx); + } + + fn cycle_thread_impl(&mut self, forward: bool, window: &mut Window, cx: &mut Context) { + let thread_indices: Vec = self + .contents + .entries + .iter() + .enumerate() + .filter_map(|(ix, entry)| match entry { + ListEntry::Thread(_) => Some(ix), + _ => None, + }) + .collect(); + + if thread_indices.is_empty() { + return; + } + + let current_thread_pos = self.active_entry.as_ref().and_then(|active| { + thread_indices + .iter() + .position(|&ix| active.matches_entry(&self.contents.entries[ix])) + }); + + let next_pos = match current_thread_pos { + Some(pos) => { + let count = thread_indices.len(); + if forward { + (pos + 1) % count + } else { + (pos + count - 1) % count + } + } + None => 0, + }; + + let entry_ix = thread_indices[next_pos]; + let ListEntry::Thread(thread) = &self.contents.entries[entry_ix] else { + return; + }; + + let metadata = thread.metadata.clone(); + match &thread.workspace { + ThreadEntryWorkspace::Open(workspace) => { + let workspace = workspace.clone(); + self.activate_thread(metadata, &workspace, true, window, cx); + } + ThreadEntryWorkspace::Closed(path_list) => { + self.open_workspace_and_activate_thread(metadata, path_list.clone(), window, cx); + } + } + } + + fn on_next_thread(&mut self, _: &NextThread, window: &mut Window, cx: &mut Context) { + self.cycle_thread_impl(true, window, cx); + } + + fn on_previous_thread( + &mut self, + _: &PreviousThread, + window: &mut Window, + cx: &mut Context, + ) { + self.cycle_thread_impl(false, window, cx); + } + + fn expand_thread_group(&mut self, path_list: &PathList, cx: &mut Context) { + let current = self.expanded_groups.get(path_list).copied().unwrap_or(0); + self.expanded_groups.insert(path_list.clone(), current + 1); + self.serialize(cx); + self.update_entries(cx); + } + + fn reset_thread_group_expansion(&mut self, path_list: &PathList, cx: &mut Context) { + self.expanded_groups.remove(path_list); + self.serialize(cx); + self.update_entries(cx); + } + + fn collapse_thread_group(&mut self, path_list: &PathList, cx: &mut Context) { + match self.expanded_groups.get(path_list).copied() { + Some(batches) if batches > 1 => { + self.expanded_groups.insert(path_list.clone(), batches - 1); + } + Some(_) => { + self.expanded_groups.remove(path_list); + } + None => return, + } + self.serialize(cx); + self.update_entries(cx); + } + + fn on_show_more_threads( + &mut self, + _: &ShowMoreThreads, + _window: &mut Window, + cx: &mut Context, + ) { + let Some(active_key) = self.active_project_group_key(cx) else { + return; + }; + self.expand_thread_group(active_key.path_list(), cx); + } + + fn on_show_fewer_threads( + &mut self, + _: &ShowFewerThreads, + _window: &mut Window, + cx: &mut Context, + ) { + let Some(active_key) = self.active_project_group_key(cx) else { + return; + }; + self.collapse_thread_group(active_key.path_list(), cx); + } + + fn on_new_thread( + &mut self, + _: &workspace::NewThread, + window: &mut Window, + cx: &mut Context, + ) { + let Some(workspace) = self.active_workspace(cx) else { + return; + }; + self.create_new_thread(&workspace, window, cx); + } + + fn on_move_workspace_to_new_window( + &mut self, + _: &MoveWorkspaceToNewWindow, + window: &mut Window, + cx: &mut Context, + ) { + let Some(multi_workspace) = self.multi_workspace.upgrade() else { + return; + }; + + let group_count = multi_workspace.read(cx).project_group_keys().count(); + if group_count <= 1 { + return; + } + + let Some(active_key) = self.active_project_group_key(cx) else { + return; + }; + + multi_workspace.update(cx, |multi_workspace, cx| { + multi_workspace.move_project_group_to_new_window(&active_key, window, cx); + }); + } + fn render_draft_thread( &self, ix: usize, @@ -3618,6 +3853,18 @@ impl WorkspaceSidebar for Sidebar { self.toggle_thread_switcher_impl(select_last, window, cx); } + fn cycle_project_group(&mut self, forward: bool, window: &mut Window, cx: &mut Context) { + self.cycle_project_group_impl(forward, window, cx); + } + + fn cycle_thread(&mut self, forward: bool, window: &mut Window, cx: &mut Context) { + self.cycle_thread_impl(forward, window, cx); + } + + fn move_workspace_to_new_window(&mut self, window: &mut Window, cx: &mut Context) { + self.on_move_workspace_to_new_window(&MoveWorkspaceToNewWindow, window, cx); + } + fn serialized_state(&self, _cx: &App) -> Option { let serialized = SerializedSidebar { width: Some(f32::from(self.width)), @@ -3713,6 +3960,14 @@ impl Render for Sidebar { .on_action(cx.listener(Self::toggle_archive)) .on_action(cx.listener(Self::focus_sidebar_filter)) .on_action(cx.listener(Self::on_toggle_thread_switcher)) + .on_action(cx.listener(Self::on_next_project_group)) + .on_action(cx.listener(Self::on_previous_project_group)) + .on_action(cx.listener(Self::on_next_thread)) + .on_action(cx.listener(Self::on_previous_thread)) + .on_action(cx.listener(Self::on_show_more_threads)) + .on_action(cx.listener(Self::on_show_fewer_threads)) + .on_action(cx.listener(Self::on_new_thread)) + .on_action(cx.listener(Self::on_move_workspace_to_new_window)) .on_action(cx.listener(|this, _: &OpenRecent, window, cx| { this.recent_projects_popover_handle.toggle(window, cx); })) diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index e1462f5307546a50fda9fb55819b3570e5106365..eb37c6fd1c22d140ba085631deded64af893aae0 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -2054,6 +2054,7 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { archived: false, }, &workspace_a, + false, window, cx, ); @@ -2109,6 +2110,7 @@ async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { archived: false, }, &workspace_b, + false, window, cx, ); diff --git a/crates/sidebar/src/thread_switcher.rs b/crates/sidebar/src/thread_switcher.rs index 86e2aeba38b9ee18a8f56597abc0d62f5741b714..d525f6d67838c82e0e222fa4755227664f93d166 100644 --- a/crates/sidebar/src/thread_switcher.rs +++ b/crates/sidebar/src/thread_switcher.rs @@ -126,6 +126,10 @@ impl ThreadSwitcher { } fn confirm(&mut self, _: &menu::Confirm, _window: &mut gpui::Window, cx: &mut Context) { + self.confirm_selected(cx); + } + + fn confirm_selected(&mut self, cx: &mut Context) { if let Some(entry) = self.entries.get(self.selected_index) { cx.emit(ThreadSwitcherEvent::Confirmed { metadata: entry.metadata.clone(), @@ -135,6 +139,13 @@ impl ThreadSwitcher { cx.emit(DismissEvent); } + fn select_and_confirm(&mut self, index: usize, cx: &mut Context) { + if index < self.entries.len() { + self.selected_index = index; + self.confirm_selected(cx); + } + } + fn cancel(&mut self, _: &menu::Cancel, _window: &mut gpui::Window, cx: &mut Context) { cx.emit(ThreadSwitcherEvent::Dismissed); cx.emit(DismissEvent); @@ -202,28 +213,37 @@ impl Render for ThreadSwitcher { .children(self.entries.iter().enumerate().map(|(ix, entry)| { let id = SharedString::from(format!("thread-switcher-{}", entry.session_id)); - ThreadItem::new(id, entry.title.clone()) - .rounded(true) - .icon(entry.icon) - .status(entry.status) - .when_some(entry.icon_from_external_svg.clone(), |this, svg| { - this.custom_icon_from_external_svg(svg) - }) - .when_some(entry.project_name.clone(), |this, name| { - this.project_name(name) - }) - .worktrees(entry.worktrees.clone()) - .timestamp(entry.timestamp.clone()) - .title_generating(entry.is_title_generating) - .notified(entry.notified) - .when(entry.diff_stats.lines_added > 0, |this| { - this.added(entry.diff_stats.lines_added as usize) - }) - .when(entry.diff_stats.lines_removed > 0, |this| { - this.removed(entry.diff_stats.lines_removed as usize) - }) - .selected(ix == selected_index) - .base_bg(cx.theme().colors().surface_background) + div() + .id(id.clone()) + .on_click( + cx.listener(move |this, _event: &gpui::ClickEvent, _window, cx| { + this.select_and_confirm(ix, cx); + }), + ) + .child( + ThreadItem::new(id, entry.title.clone()) + .rounded(true) + .icon(entry.icon) + .status(entry.status) + .when_some(entry.icon_from_external_svg.clone(), |this, svg| { + this.custom_icon_from_external_svg(svg) + }) + .when_some(entry.project_name.clone(), |this, name| { + this.project_name(name) + }) + .worktrees(entry.worktrees.clone()) + .timestamp(entry.timestamp.clone()) + .title_generating(entry.is_title_generating) + .notified(entry.notified) + .when(entry.diff_stats.lines_added > 0, |this| { + this.added(entry.diff_stats.lines_added as usize) + }) + .when(entry.diff_stats.lines_removed > 0, |this| { + this.removed(entry.diff_stats.lines_removed as usize) + }) + .selected(ix == selected_index) + .base_bg(cx.theme().colors().elevated_surface_background), + ) .into_any_element() })) } diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 1b057e3fb1e3b5e0639e4a44462fc7528f6db85d..a52246d3c40288e08e70ca5da3789d4493df3a44 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use ui::prelude::*; use util::ResultExt; use util::path_list::PathList; -use zed_actions::agents_sidebar::{MoveWorkspaceToNewWindow, ToggleThreadSwitcher}; +use zed_actions::agents_sidebar::ToggleThreadSwitcher; use agent_settings::AgentSettings; use settings::SidebarDockPosition; @@ -40,7 +40,22 @@ actions!( CloseWorkspaceSidebar, /// Moves focus to or from the workspace sidebar without closing it. FocusWorkspaceSidebar, - //TODO: Restore next/previous workspace + /// Activates the next project group in the sidebar. + NextProjectGroup, + /// Activates the previous project group in the sidebar. + PreviousProjectGroup, + /// Activates the next thread in sidebar order. + NextThread, + /// Activates the previous thread in sidebar order. + PreviousThread, + /// Expands the thread list for the current project to show more threads. + ShowMoreThreads, + /// Collapses the thread list for the current project to show fewer threads. + ShowFewerThreads, + /// Creates a new thread in the current workspace. + NewThread, + /// Moves the current workspace's project group to a new window. + MoveWorkspaceToNewWindow, ] ); @@ -114,6 +129,21 @@ pub trait Sidebar: Focusable + Render + EventEmitter + Sized { ) { } + /// Activates the next or previous project group. + fn cycle_project_group( + &mut self, + _forward: bool, + _window: &mut Window, + _cx: &mut Context, + ) { + } + + /// Activates the next or previous thread in sidebar order. + fn cycle_thread(&mut self, _forward: bool, _window: &mut Window, _cx: &mut Context) {} + + /// Moves the active workspace's project group to a new window. + fn move_workspace_to_new_window(&mut self, _window: &mut Window, _cx: &mut Context) {} + /// Return an opaque JSON blob of sidebar-specific state to persist. fn serialized_state(&self, _cx: &App) -> Option { None @@ -139,6 +169,9 @@ pub trait SidebarHandle: 'static + Send + Sync { fn to_any(&self) -> AnyView; fn entity_id(&self) -> EntityId; fn toggle_thread_switcher(&self, select_last: bool, window: &mut Window, cx: &mut App); + fn cycle_project_group(&self, forward: bool, window: &mut Window, cx: &mut App); + fn cycle_thread(&self, forward: bool, window: &mut Window, cx: &mut App); + fn move_workspace_to_new_window(&self, window: &mut Window, cx: &mut App); fn is_threads_list_view_active(&self, cx: &App) -> bool; @@ -199,6 +232,33 @@ impl SidebarHandle for Entity { }); } + fn cycle_project_group(&self, forward: bool, window: &mut Window, cx: &mut App) { + let entity = self.clone(); + window.defer(cx, move |window, cx| { + entity.update(cx, |this, cx| { + this.cycle_project_group(forward, window, cx); + }); + }); + } + + fn cycle_thread(&self, forward: bool, window: &mut Window, cx: &mut App) { + let entity = self.clone(); + window.defer(cx, move |window, cx| { + entity.update(cx, |this, cx| { + this.cycle_thread(forward, window, cx); + }); + }); + } + + fn move_workspace_to_new_window(&self, window: &mut Window, cx: &mut App) { + let entity = self.clone(); + window.defer(cx, move |window, cx| { + entity.update(cx, |this, cx| { + this.move_workspace_to_new_window(window, cx); + }); + }); + } + fn is_threads_list_view_active(&self, cx: &App) -> bool { self.read(cx).is_threads_list_view_active() } @@ -826,6 +886,19 @@ impl MultiWorkspace { cx.notify(); } + /// Promotes the currently active workspace to persistent if it is + /// transient, so it is retained across workspace switches even when + /// the sidebar is closed. No-op if the workspace is already persistent. + pub fn retain_active_workspace(&mut self, cx: &mut Context) { + if let ActiveWorkspace::Transient(workspace) = &self.active_workspace { + let workspace = workspace.clone(); + let index = self.promote_transient(workspace, cx); + self.active_workspace = ActiveWorkspace::Persistent(index); + self.serialize(cx); + cx.notify(); + } + } + /// Promotes a former transient workspace into the persistent list. /// Returns the index of the newly inserted workspace. fn promote_transient(&mut self, workspace: Entity, cx: &mut Context) -> usize { @@ -1300,16 +1373,6 @@ impl MultiWorkspace { }); } - fn move_active_workspace_to_new_window( - &mut self, - _: &MoveWorkspaceToNewWindow, - window: &mut Window, - cx: &mut Context, - ) { - let workspace = self.workspace().clone(); - self.move_workspace_to_new_window(&workspace, window, cx); - } - pub fn open_project( &mut self, paths: Vec, @@ -1443,7 +1506,6 @@ impl Render for MultiWorkspace { this.focus_sidebar(window, cx); }, )) - .on_action(cx.listener(Self::move_active_workspace_to_new_window)) .on_action(cx.listener( |this: &mut Self, action: &ToggleThreadSwitcher, window, cx| { if let Some(sidebar) = &this.sidebar { @@ -1451,6 +1513,39 @@ impl Render for MultiWorkspace { } }, )) + .on_action( + cx.listener(|this: &mut Self, _: &NextProjectGroup, window, cx| { + if let Some(sidebar) = &this.sidebar { + sidebar.cycle_project_group(true, window, cx); + } + }), + ) + .on_action(cx.listener( + |this: &mut Self, _: &PreviousProjectGroup, window, cx| { + if let Some(sidebar) = &this.sidebar { + sidebar.cycle_project_group(false, window, cx); + } + }, + )) + .on_action(cx.listener(|this: &mut Self, _: &NextThread, window, cx| { + if let Some(sidebar) = &this.sidebar { + sidebar.cycle_thread(true, window, cx); + } + })) + .on_action( + cx.listener(|this: &mut Self, _: &PreviousThread, window, cx| { + if let Some(sidebar) = &this.sidebar { + sidebar.cycle_thread(false, window, cx); + } + }), + ) + .on_action(cx.listener( + |this: &mut Self, _: &MoveWorkspaceToNewWindow, window, cx| { + if let Some(sidebar) = &this.sidebar { + sidebar.move_workspace_to_new_window(window, cx); + } + }, + )) }) .when( self.sidebar_open() && self.multi_workspace_enabled(cx), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7979ffe828cbf8c4da5a40a29eaa6537f1433c3c..ba4c81592d3b6e4030d45cb5da1dc4299673d14a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,9 +31,11 @@ mod workspace_settings; pub use crate::notifications::NotificationFrame; pub use dock::Panel; pub use multi_workspace::{ - CloseWorkspaceSidebar, DraggedSidebar, FocusWorkspaceSidebar, MultiWorkspace, - MultiWorkspaceEvent, Sidebar, SidebarEvent, SidebarHandle, SidebarRenderState, SidebarSide, - ToggleWorkspaceSidebar, sidebar_side_context_menu, + CloseWorkspaceSidebar, DraggedSidebar, FocusWorkspaceSidebar, MoveWorkspaceToNewWindow, + MultiWorkspace, MultiWorkspaceEvent, NewThread, NextProjectGroup, NextThread, + PreviousProjectGroup, PreviousThread, ShowFewerThreads, ShowMoreThreads, Sidebar, SidebarEvent, + SidebarHandle, SidebarRenderState, SidebarSide, ToggleWorkspaceSidebar, + sidebar_side_context_menu, }; pub use path_list::{PathList, SerializedPathList}; pub use toast_layer::{ToastAction, ToastLayer, ToastView}; @@ -4848,12 +4850,31 @@ impl Workspace { .as_ref() .map(|h| Target::Sidebar(h.clone())); + let sidebar_on_right = self + .multi_workspace + .as_ref() + .and_then(|mw| mw.upgrade()) + .map_or(false, |mw| { + mw.read(cx).sidebar_side(cx) == SidebarSide::Right + }); + + let away_from_sidebar = if sidebar_on_right { + SplitDirection::Left + } else { + SplitDirection::Right + }; + + let (near_dock, far_dock) = if sidebar_on_right { + (&self.right_dock, &self.left_dock) + } else { + (&self.left_dock, &self.right_dock) + }; + let target = match (origin, direction) { - // From the sidebar, only Right navigates into the workspace. - (Origin::Sidebar, SplitDirection::Right) => try_dock(&self.left_dock) + (Origin::Sidebar, dir) if dir == away_from_sidebar => try_dock(near_dock) .or_else(|| get_last_active_pane().map(Target::Pane)) .or_else(|| try_dock(&self.bottom_dock)) - .or_else(|| try_dock(&self.right_dock)), + .or_else(|| try_dock(far_dock)), (Origin::Sidebar, _) => None, @@ -4866,8 +4887,22 @@ impl Workspace { match direction { SplitDirection::Up => None, SplitDirection::Down => try_dock(&self.bottom_dock), - SplitDirection::Left => try_dock(&self.left_dock).or(sidebar_target), - SplitDirection::Right => try_dock(&self.right_dock), + SplitDirection::Left => { + let dock_target = try_dock(&self.left_dock); + if sidebar_on_right { + dock_target + } else { + dock_target.or(sidebar_target) + } + } + SplitDirection::Right => { + let dock_target = try_dock(&self.right_dock); + if sidebar_on_right { + dock_target.or(sidebar_target) + } else { + dock_target + } + } } } } @@ -4880,24 +4915,48 @@ impl Workspace { } } - (Origin::LeftDock, SplitDirection::Left) => sidebar_target, + (Origin::LeftDock, SplitDirection::Left) => { + if sidebar_on_right { + None + } else { + sidebar_target + } + } (Origin::LeftDock, SplitDirection::Down) | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock), (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane), (Origin::BottomDock, SplitDirection::Left) => { - try_dock(&self.left_dock).or(sidebar_target) + let dock_target = try_dock(&self.left_dock); + if sidebar_on_right { + dock_target + } else { + dock_target.or(sidebar_target) + } + } + (Origin::BottomDock, SplitDirection::Right) => { + let dock_target = try_dock(&self.right_dock); + if sidebar_on_right { + dock_target.or(sidebar_target) + } else { + dock_target + } } - (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock), (Origin::RightDock, SplitDirection::Left) => { if let Some(last_active_pane) = get_last_active_pane() { Some(Target::Pane(last_active_pane)) } else { - try_dock(&self.bottom_dock) - .or_else(|| try_dock(&self.left_dock)) - .or(sidebar_target) + try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock)) + } + } + + (Origin::RightDock, SplitDirection::Right) => { + if sidebar_on_right { + sidebar_target + } else { + None } } diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 66ccf9c41c1e1cfcb821e03b4e9b7d4803f53c0b..0a75471da974638a330c8786306c2010508fbebd 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -785,8 +785,6 @@ pub mod agents_sidebar { [ /// Moves focus to the sidebar's search/filter editor. FocusSidebarFilter, - /// Moves the active workspace to a new window. - MoveWorkspaceToNewWindow, ] ); }