diff --git a/Cargo.lock b/Cargo.lock index 3d2ade2ddeab584b8a7ea45590248bdf97e89e57..b9b048468cbc4f52b86b1cd0f1b0a9d3d0f4d9e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15825,6 +15825,7 @@ dependencies = [ "language_model", "menu", "project", + "recent_projects", "serde_json", "settings", "theme", diff --git a/crates/sidebar/Cargo.toml b/crates/sidebar/Cargo.toml index d835e9a602d7610eb412d8e3fc4135cb55d5a634..36a8d1cf085e544d38d903fe63f514539287dcc5 100644 --- a/crates/sidebar/Cargo.toml +++ b/crates/sidebar/Cargo.toml @@ -26,6 +26,7 @@ fs.workspace = true gpui.workspace = true menu.workspace = true project.workspace = true +recent_projects.workspace = true settings.workspace = true theme.workspace = true ui.workspace = true diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 1e50a75e2841fb471b2d630b71c2df59200c5bea..4dbc2f811a62c266bc34708cd3b8bd1377938d4d 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -12,20 +12,23 @@ use gpui::{ }; use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious}; use project::Event as ProjectEvent; +use recent_projects::RecentProjects; use settings::Settings; use std::collections::{HashMap, HashSet}; use std::mem; use theme::{ActiveTheme, ThemeSettings}; use ui::utils::TRAFFIC_LIGHT_PADDING; use ui::{ - AgentThreadStatus, GradientFade, HighlightedLabel, IconButtonShape, KeyBinding, ListItem, Tab, - ThreadItem, Tooltip, WithScrollbar, prelude::*, + AgentThreadStatus, ButtonStyle, GradientFade, HighlightedLabel, IconButtonShape, KeyBinding, + ListItem, PopoverMenu, PopoverMenuHandle, Tab, ThreadItem, TintColor, Tooltip, WithScrollbar, + prelude::*, }; use util::path_list::PathList; use workspace::{ FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, Sidebar as WorkspaceSidebar, SidebarEvent, ToggleWorkspaceSidebar, Workspace, }; +use zed_actions::OpenRecent; use zed_actions::editor::{MoveDown, MoveUp}; actions!( @@ -183,6 +186,7 @@ pub struct Sidebar { active_entry_index: Option, collapsed_groups: HashSet, expanded_groups: HashMap, + recent_projects_popover_handle: PopoverMenuHandle, } impl EventEmitter for Sidebar {} @@ -278,6 +282,7 @@ impl Sidebar { active_entry_index: None, collapsed_groups: HashSet::new(), expanded_groups: HashMap::new(), + recent_projects_popover_handle: PopoverMenuHandle::default(), } } @@ -1174,6 +1179,48 @@ impl Sidebar { .into_any_element() } + fn render_recent_projects_button(&self, cx: &mut Context) -> impl IntoElement { + let workspace = self + .multi_workspace + .upgrade() + .map(|mw| mw.read(cx).workspace().downgrade()); + + let focus_handle = workspace + .as_ref() + .and_then(|ws| ws.upgrade()) + .map(|w| w.read(cx).focus_handle(cx)) + .unwrap_or_else(|| cx.focus_handle()); + + let popover_handle = self.recent_projects_popover_handle.clone(); + + PopoverMenu::new("sidebar-recent-projects-menu") + .with_handle(popover_handle) + .menu(move |window, cx| { + workspace.as_ref().map(|ws| { + RecentProjects::popover(ws.clone(), false, focus_handle.clone(), window, cx) + }) + }) + .trigger_with_tooltip( + IconButton::new("open-project", IconName::OpenFolder) + .icon_size(IconSize::Small) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)), + |_window, cx| { + Tooltip::for_action( + "Recent Projects", + &OpenRecent { + create_new_window: false, + }, + cx, + ) + }, + ) + .anchor(gpui::Corner::TopLeft) + .offset(gpui::Point { + x: px(0.0), + y: px(2.0), + }) + } + fn render_filter_input(&self, cx: &mut Context) -> impl IntoElement { let settings = ThemeSettings::get_global(cx); let text_style = TextStyle { @@ -1315,6 +1362,14 @@ impl WorkspaceSidebar for Sidebar { fn has_notifications(&self, _cx: &App) -> bool { !self.contents.notified_threads.is_empty() } + + fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) { + self.recent_projects_popover_handle.toggle(window, cx); + } + + fn is_recent_projects_popover_deployed(&self) -> bool { + self.recent_projects_popover_handle.is_deployed() + } } impl Focusable for Sidebar { @@ -1412,27 +1467,7 @@ impl Render for Sidebar { cx.emit(SidebarEvent::Close); })) }) - .child( - IconButton::new("open-project", IconName::OpenFolder) - .icon_size(IconSize::Small) - .tooltip(|_window, cx| { - Tooltip::for_action( - "Open Project", - &workspace::Open { - create_new_window: false, - }, - cx, - ) - }) - .on_click(|_event, window, cx| { - window.dispatch_action( - Box::new(workspace::Open { - create_new_window: false, - }), - cx, - ); - }), - ), + .child(self.render_recent_projects_button(cx)), ) .child( h_flex() diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 3566d6210769c09a8a6de1706cb258ff2b119ce9..96cc929c06039c14a9ce4eaa05fd067fbd95b7d0 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -151,6 +151,7 @@ pub struct TitleBar { user_store: Entity, client: Arc, workspace: WeakEntity, + multi_workspace: Option>, application_menu: Option>, _subscriptions: Vec, banner: Entity, @@ -188,7 +189,7 @@ impl Render for TitleBar { .when(title_bar_settings.show_project_items, |title_bar| { title_bar .children(self.render_project_host(cx)) - .child(self.render_project_name(cx)) + .child(self.render_project_name(window, cx)) }) .when(title_bar_settings.show_branch_name, |title_bar| { title_bar.children(self.render_project_branch(cx)) @@ -389,6 +390,7 @@ impl TitleBar { if let Some(this) = this.upgrade() { this.update(cx, |this, _| { this._subscriptions.push(subscription); + this.multi_workspace = Some(multi_workspace.downgrade()); }); } }); @@ -400,6 +402,7 @@ impl TitleBar { platform_titlebar, application_menu, workspace: workspace.weak_handle(), + multi_workspace: None, project, user_store, client, @@ -718,7 +721,11 @@ impl TitleBar { ) } - pub fn render_project_name(&self, cx: &mut Context) -> impl IntoElement { + pub fn render_project_name( + &self, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { let workspace = self.workspace.clone(); let name = self.effective_active_worktree(cx).map(|worktree| { @@ -734,6 +741,19 @@ impl TitleBar { "Open Recent Project".to_string() }; + let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open(); + + if is_sidebar_open { + return self + .render_project_name_with_sidebar_popover( + window, + display_name, + is_project_selected, + cx, + ) + .into_any_element(); + } + let focus_handle = workspace .upgrade() .map(|w| w.read(cx).focus_handle(cx)) @@ -773,6 +793,49 @@ impl TitleBar { .into_any_element() } + fn render_project_name_with_sidebar_popover( + &self, + _window: &Window, + display_name: String, + is_project_selected: bool, + cx: &mut Context, + ) -> impl IntoElement { + let multi_workspace = self.multi_workspace.clone(); + + let is_popover_deployed = multi_workspace + .as_ref() + .and_then(|mw| mw.upgrade()) + .map(|mw| mw.read(cx).is_recent_projects_popover_deployed(cx)) + .unwrap_or(false); + + Button::new("project_name_trigger", display_name) + .label_size(LabelSize::Small) + .when(self.worktree_count(cx) > 1, |this| { + this.icon(IconName::ChevronDown) + .icon_color(Color::Muted) + .icon_size(IconSize::XSmall) + }) + .toggle_state(is_popover_deployed) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + .when(!is_project_selected, |s| s.color(Color::Muted)) + .tooltip(move |_window, cx| { + Tooltip::for_action( + "Recent Projects", + &zed_actions::OpenRecent { + create_new_window: false, + }, + cx, + ) + }) + .on_click(move |_, window, cx| { + if let Some(mw) = multi_workspace.as_ref().and_then(|mw| mw.upgrade()) { + mw.update(cx, |mw, cx| { + mw.toggle_recent_projects_popover(window, cx); + }); + } + }) + } + pub fn render_project_branch(&self, cx: &mut Context) -> Option { let effective_worktree = self.effective_active_worktree(cx)?; let repository = self.get_repository_for_worktree(&effective_worktree, cx)?; diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 3f5981178fe118f41196538e1a22960bd55644d0..26af1ce27ecc28b7b541625a16731d0d721a7fc9 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -50,6 +50,8 @@ pub trait Sidebar: EventEmitter + Focusable + Render + Sized { fn width(&self, cx: &App) -> Pixels; fn set_width(&mut self, width: Option, cx: &mut Context); fn has_notifications(&self, cx: &App) -> bool; + fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App); + fn is_recent_projects_popover_deployed(&self) -> bool; } pub trait SidebarHandle: 'static + Send + Sync { @@ -60,6 +62,8 @@ pub trait SidebarHandle: 'static + Send + Sync { fn has_notifications(&self, cx: &App) -> bool; fn to_any(&self) -> AnyView; fn entity_id(&self) -> EntityId; + fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App); + fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool; } #[derive(Clone)] @@ -100,6 +104,16 @@ impl SidebarHandle for Entity { fn entity_id(&self) -> EntityId { Entity::entity_id(self) } + + fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) { + self.update(cx, |this, cx| { + this.toggle_recent_projects_popover(window, cx); + }); + } + + fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool { + self.read(cx).is_recent_projects_popover_deployed() + } } pub struct MultiWorkspace { @@ -187,6 +201,18 @@ impl MultiWorkspace { .map_or(false, |s| s.has_notifications(cx)) } + pub fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) { + if let Some(sidebar) = &self.sidebar { + sidebar.toggle_recent_projects_popover(window, cx); + } + } + + pub fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool { + self.sidebar + .as_ref() + .map_or(false, |s| s.is_recent_projects_popover_deployed(cx)) + } + pub fn multi_workspace_enabled(&self, cx: &App) -> bool { cx.has_flag::() && !DisableAiSettings::get_global(cx).disable_ai }