Detailed changes
@@ -15980,6 +15980,7 @@ dependencies = [
"action_log",
"agent",
"agent-client-protocol",
+ "agent_settings",
"agent_ui",
"anyhow",
"assistant_text_thread",
@@ -21516,6 +21517,7 @@ dependencies = [
name = "workspace"
version = "0.1.0"
dependencies = [
+ "agent_settings",
"any_vec",
"anyhow",
"async-recursion",
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect opacity="0.1" width="5" height="12" rx="2" transform="matrix(-1 0 0 1 14 2)" fill="#C6CAD0"/>
+<path d="M9 2V14" stroke="#C6CAD0" stroke-width="1.2"/>
+<rect x="2" y="2" width="12" height="12" rx="1.5" stroke="#C6CAD0" stroke-width="1.2"/>
+</svg>
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect opacity="0.8" width="5" height="12" rx="2" transform="matrix(-1 0 0 1 14 2)" fill="#C6CAD0"/>
+<path d="M9 2V14" stroke="#C6CAD0" stroke-width="1.2"/>
+<rect x="2" y="2" width="12" height="12" rx="1.5" stroke="#C6CAD0" stroke-width="1.2"/>
+</svg>
@@ -943,6 +943,8 @@
"button": true,
// Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
"dock": "right",
+ // Where to position the sidebar. Can be 'left', 'right', or 'follow_agent'.
+ "sidebar_side": "follow_agent",
// Default width when the agent panel is docked to the left or right.
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
@@ -596,6 +596,7 @@ mod tests {
tool_permissions,
show_turn_stats: false,
new_thread_location: Default::default(),
+ sidebar_side: Default::default(),
}
}
@@ -12,7 +12,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
- NewThreadLocation, NotifyWhenAgentWaiting, RegisterSetting, Settings, ToolPermissionMode,
+ NewThreadLocation, NotifyWhenAgentWaiting, RegisterSetting, Settings, SidebarDockPosition,
+ SidebarSide, ToolPermissionMode,
};
pub use crate::agent_profile::*;
@@ -26,6 +27,7 @@ pub struct AgentSettings {
pub enabled: bool,
pub button: bool,
pub dock: DockPosition,
+ pub sidebar_side: SidebarDockPosition,
pub default_width: Pixels,
pub default_height: Pixels,
pub default_model: Option<LanguageModelSelection>,
@@ -77,6 +79,17 @@ impl AgentSettings {
return None;
}
+ pub fn sidebar_side(&self) -> SidebarSide {
+ match self.sidebar_side {
+ SidebarDockPosition::Left => SidebarSide::Left,
+ SidebarDockPosition::Right => SidebarSide::Right,
+ SidebarDockPosition::FollowAgent => match self.dock {
+ DockPosition::Right => SidebarSide::Right,
+ _ => SidebarSide::Left,
+ },
+ }
+ }
+
pub fn set_message_editor_max_lines(&self) -> usize {
self.message_editor_min_lines * 2
}
@@ -407,6 +420,7 @@ impl Settings for AgentSettings {
enabled: agent.enabled.unwrap(),
button: agent.button.unwrap(),
dock: agent.dock.unwrap(),
+ sidebar_side: agent.sidebar_side.unwrap(),
default_width: px(agent.default_width.unwrap()),
default_height: px(agent.default_height.unwrap()),
default_model: Some(agent.default_model.unwrap()),
@@ -3186,13 +3186,17 @@ impl Panel for AgentPanel {
}
fn activation_priority(&self) -> u32 {
- 8
+ 0
}
fn enabled(&self, cx: &App) -> bool {
AgentSettings::get_global(cx).enabled(cx)
}
+ fn is_agent_panel(&self) -> bool {
+ true
+ }
+
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
self.zoomed
}
@@ -648,7 +648,6 @@ mod tests {
default_profile: AgentProfileId::default(),
default_view: DefaultAgentView::Thread,
profiles: Default::default(),
-
notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
play_sound_when_agent_done: false,
single_file_review: false,
@@ -662,6 +661,7 @@ mod tests {
tool_permissions: Default::default(),
show_turn_stats: false,
new_thread_location: Default::default(),
+ sidebar_side: Default::default(),
};
cx.update(|cx| {
@@ -7,6 +7,7 @@ use crate::{
use acp_thread::AgentSessionInfo;
use agent::ThreadStore;
use agent_client_protocol as acp;
+use agent_settings::AgentSettings;
use chrono::{DateTime, Datelike as _, Local, NaiveDate, TimeDelta, Utc};
use editor::Editor;
use fs::Fs;
@@ -17,6 +18,7 @@ use gpui::{
use itertools::Itertools as _;
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
use project::{AgentId, AgentServerStore};
+use settings::Settings as _;
use theme::ActiveTheme;
use ui::{
ButtonLike, CommonAnimationExt, ContextMenu, ContextMenuEntry, Divider, HighlightedLabel,
@@ -795,7 +797,12 @@ impl ThreadsArchiveView {
fn render_header(&self, window: &Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_query = !self.filter_editor.read(cx).text(cx).is_empty();
- let traffic_lights = cfg!(target_os = "macos") && !window.is_fullscreen();
+ let sidebar_on_left = matches!(
+ AgentSettings::get_global(cx).sidebar_side(),
+ settings::SidebarSide::Left
+ );
+ let traffic_lights =
+ cfg!(target_os = "macos") && !window.is_fullscreen() && sidebar_on_left;
let header_height = platform_title_bar_height(window);
let show_focus_keybinding =
self.selection.is_some() && !self.filter_editor.focus_handle(cx).is_focused(window);
@@ -804,15 +811,21 @@ impl ThreadsArchiveView {
.h(header_height)
.mt_px()
.pb_px()
- .when(traffic_lights, |this| {
- this.pl(px(ui::utils::TRAFFIC_LIGHT_PADDING))
+ .map(|this| {
+ if traffic_lights {
+ this.pl(px(ui::utils::TRAFFIC_LIGHT_PADDING))
+ } else {
+ this.pl_1p5()
+ }
})
.pr_1p5()
.gap_1()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border)
- .child(Divider::vertical().color(ui::DividerColor::Border))
+ .when(traffic_lights, |this| {
+ this.child(Divider::vertical().color(ui::DividerColor::Border))
+ })
.child(
h_flex()
.ml_1()
@@ -640,7 +640,7 @@ impl Panel for NotificationPanel {
}
fn activation_priority(&self) -> u32 {
- 3
+ 4
}
}
@@ -1601,7 +1601,7 @@ impl Panel for DebugPanel {
}
fn activation_priority(&self) -> u32 {
- 9
+ 7
}
fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
@@ -5818,7 +5818,7 @@ impl Panel for GitPanel {
}
fn activation_priority(&self) -> u32 {
- 2
+ 3
}
}
@@ -244,6 +244,8 @@ pub enum IconName {
ThreadFromSummary,
ThreadsSidebarLeftClosed,
ThreadsSidebarLeftOpen,
+ ThreadsSidebarRightClosed,
+ ThreadsSidebarRightOpen,
ThumbsDown,
ThumbsUp,
TodoComplete,
@@ -4,8 +4,8 @@ mod system_window_tabs;
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
use gpui::{
Action, AnyElement, App, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement,
- MouseButton, ParentElement, StatefulInteractiveElement, Styled, Window, WindowButtonLayout,
- WindowControlArea, div, px,
+ MouseButton, ParentElement, StatefulInteractiveElement, Styled, WeakEntity, Window,
+ WindowButtonLayout, WindowControlArea, div, px,
};
use project::DisableAiSettings;
use settings::Settings;
@@ -15,6 +15,7 @@ use ui::{
prelude::*,
utils::{TRAFFIC_LIGHT_PADDING, platform_title_bar_height},
};
+use workspace::{MultiWorkspace, SidebarRenderState, SidebarSide};
use crate::{
platforms::{platform_linux, platform_windows},
@@ -32,7 +33,7 @@ pub struct PlatformTitleBar {
should_move: bool,
system_window_tabs: Entity<SystemWindowTabs>,
button_layout: Option<WindowButtonLayout>,
- workspace_sidebar_open: bool,
+ multi_workspace: Option<WeakEntity<MultiWorkspace>>,
}
impl PlatformTitleBar {
@@ -47,10 +48,19 @@ impl PlatformTitleBar {
should_move: false,
system_window_tabs,
button_layout: None,
- workspace_sidebar_open: false,
+ multi_workspace: None,
}
}
+ pub fn with_multi_workspace(mut self, multi_workspace: WeakEntity<MultiWorkspace>) -> Self {
+ self.multi_workspace = Some(multi_workspace);
+ self
+ }
+
+ pub fn set_multi_workspace(&mut self, multi_workspace: WeakEntity<MultiWorkspace>) {
+ self.multi_workspace = Some(multi_workspace);
+ }
+
pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
if window.is_window_active() && !self.should_move {
@@ -92,13 +102,12 @@ impl PlatformTitleBar {
SystemWindowTabs::init(cx);
}
- pub fn is_workspace_sidebar_open(&self) -> bool {
- self.workspace_sidebar_open
- }
-
- pub fn set_workspace_sidebar_open(&mut self, open: bool, cx: &mut Context<Self>) {
- self.workspace_sidebar_open = open;
- cx.notify();
+ fn sidebar_render_state(&self, cx: &App) -> SidebarRenderState {
+ self.multi_workspace
+ .as_ref()
+ .and_then(|mw| mw.upgrade())
+ .map(|mw| mw.read(cx).sidebar_render_state(cx))
+ .unwrap_or_default()
}
pub fn is_multi_workspace_enabled(cx: &App) -> bool {
@@ -116,8 +125,7 @@ impl Render for PlatformTitleBar {
let children = mem::take(&mut self.children);
let button_layout = self.effective_button_layout(&decorations, cx);
- let is_multiworkspace_sidebar_open =
- PlatformTitleBar::is_multi_workspace_enabled(cx) && self.is_workspace_sidebar_open();
+ let sidebar = self.sidebar_render_state(cx);
let title_bar = h_flex()
.window_control_area(WindowControlArea::Drag)
@@ -168,7 +176,7 @@ impl Render for PlatformTitleBar {
if window.is_fullscreen() {
this.pl_2()
} else if self.platform_style == PlatformStyle::Mac
- && !is_multiworkspace_sidebar_open
+ && !(sidebar.open && sidebar.side == SidebarSide::Left)
{
this.pl(px(TRAFFIC_LIGHT_PADDING))
} else if let Some(button_layout) =
@@ -186,11 +194,14 @@ impl Render for PlatformTitleBar {
.map(|el| match decorations {
Decorations::Server => el,
Decorations::Client { tiling, .. } => el
- .when(!(tiling.top || tiling.right), |el| {
- el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
- })
.when(
- !(tiling.top || tiling.left) && !is_multiworkspace_sidebar_open,
+ !(tiling.top || tiling.right)
+ && !(sidebar.open && sidebar.side == SidebarSide::Right),
+ |el| el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING),
+ )
+ .when(
+ !(tiling.top || tiling.left)
+ && !(sidebar.open && sidebar.side == SidebarSide::Left),
|el| el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING),
)
// this border is to avoid a transparent gap in the rounded corners
@@ -7239,7 +7239,7 @@ impl Panel for ProjectPanel {
}
fn activation_priority(&self) -> u32 {
- 0
+ 1
}
}
@@ -33,6 +33,39 @@ pub enum NewThreadLocation {
NewWorktree,
}
+/// Where to position the sidebar.
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Default,
+ PartialEq,
+ Eq,
+ Serialize,
+ Deserialize,
+ JsonSchema,
+ MergeFrom,
+ strum::VariantArray,
+ strum::VariantNames,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum SidebarDockPosition {
+ /// Always show the sidebar on the left side.
+ Left,
+ /// Always show the sidebar on the right side.
+ Right,
+ /// Show the sidebar on the same side as the agent panel.
+ #[default]
+ FollowAgent,
+}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum SidebarSide {
+ #[default]
+ Left,
+ Right,
+}
+
#[with_fallible_options]
#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)]
pub struct AgentSettingsContent {
@@ -48,6 +81,10 @@ pub struct AgentSettingsContent {
///
/// Default: right
pub dock: Option<DockPosition>,
+ /// Where to position the sidebar.
+ ///
+ /// Default: follow_agent
+ pub sidebar_side: Option<SidebarDockPosition>,
/// Default width in pixels when the agent panel is docked to the left or right.
///
/// Default: 640
@@ -157,6 +194,10 @@ impl AgentSettingsContent {
self.dock = Some(dock);
}
+ pub fn set_sidebar_side(&mut self, position: SidebarDockPosition) {
+ self.sidebar_side = Some(position);
+ }
+
pub fn set_model(&mut self, language_model: LanguageModelSelection) {
self.default_model = Some(language_model)
}
@@ -19,6 +19,7 @@ acp_thread.workspace = true
action_log.workspace = true
agent.workspace = true
agent-client-protocol.workspace = true
+agent_settings.workspace = true
agent_ui.workspace = true
anyhow.workspace = true
chrono.workspace = true
@@ -1,6 +1,7 @@
use acp_thread::ThreadStatus;
use action_log::DiffStats;
use agent_client_protocol::{self as acp};
+use agent_settings::AgentSettings;
use agent_ui::thread_metadata_store::{SidebarThreadMetadataStore, ThreadMetadata};
use agent_ui::threads_archive_view::{
ThreadsArchiveView, ThreadsArchiveViewEvent, format_history_entry_timestamp,
@@ -37,7 +38,8 @@ use util::ResultExt as _;
use util::path_list::PathList;
use workspace::{
AddFolderToProject, FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, Open,
- Sidebar as WorkspaceSidebar, ToggleWorkspaceSidebar, Workspace, WorkspaceId,
+ Sidebar as WorkspaceSidebar, SidebarSide, ToggleWorkspaceSidebar, Workspace, WorkspaceId,
+ sidebar_side_context_menu,
};
use zed_actions::OpenRecent;
@@ -2874,7 +2876,9 @@ impl Sidebar {
cx: &mut Context<Self>,
) -> impl IntoElement {
let has_query = self.has_filter_query(cx);
- let traffic_lights = cfg!(target_os = "macos") && !window.is_fullscreen();
+ let sidebar_on_left = self.side(cx) == SidebarSide::Left;
+ let traffic_lights =
+ cfg!(target_os = "macos") && !window.is_fullscreen() && sidebar_on_left;
let header_height = platform_title_bar_height(window);
h_flex()
@@ -2928,38 +2932,92 @@ impl Sidebar {
}
fn render_sidebar_toggle_button(&self, _cx: &mut Context<Self>) -> impl IntoElement {
- IconButton::new("sidebar-close-toggle", IconName::ThreadsSidebarLeftOpen)
- .icon_size(IconSize::Small)
- .tooltip(Tooltip::element(move |_window, cx| {
- v_flex()
- .gap_1()
- .child(
- h_flex()
- .gap_2()
- .justify_between()
- .child(Label::new("Toggle Sidebar"))
- .child(KeyBinding::for_action(&ToggleWorkspaceSidebar, cx)),
- )
- .child(
- h_flex()
- .pt_1()
- .gap_2()
- .border_t_1()
- .border_color(cx.theme().colors().border_variant)
- .justify_between()
- .child(Label::new("Focus Sidebar"))
- .child(KeyBinding::for_action(&FocusWorkspaceSidebar, cx)),
- )
- .into_any_element()
- }))
- .on_click(|_, window, cx| {
- if let Some(multi_workspace) = window.root::<MultiWorkspace>().flatten() {
- multi_workspace.update(cx, |multi_workspace, cx| {
- multi_workspace.close_sidebar(window, cx);
- });
- }
+ let on_right = AgentSettings::get_global(_cx).sidebar_side() == SidebarSide::Right;
+
+ sidebar_side_context_menu("sidebar-toggle-menu", _cx)
+ .anchor(if on_right {
+ gpui::Corner::BottomRight
+ } else {
+ gpui::Corner::BottomLeft
+ })
+ .attach(if on_right {
+ gpui::Corner::TopRight
+ } else {
+ gpui::Corner::TopLeft
+ })
+ .trigger(move |_is_active, _window, _cx| {
+ let icon = if on_right {
+ IconName::ThreadsSidebarRightOpen
+ } else {
+ IconName::ThreadsSidebarLeftOpen
+ };
+ IconButton::new("sidebar-close-toggle", icon)
+ .icon_size(IconSize::Small)
+ .tooltip(Tooltip::element(move |_window, cx| {
+ v_flex()
+ .gap_1()
+ .child(
+ h_flex()
+ .gap_2()
+ .justify_between()
+ .child(Label::new("Toggle Sidebar"))
+ .child(KeyBinding::for_action(&ToggleWorkspaceSidebar, cx)),
+ )
+ .child(
+ h_flex()
+ .pt_1()
+ .gap_2()
+ .border_t_1()
+ .border_color(cx.theme().colors().border_variant)
+ .justify_between()
+ .child(Label::new("Focus Sidebar"))
+ .child(KeyBinding::for_action(&FocusWorkspaceSidebar, cx)),
+ )
+ .into_any_element()
+ }))
+ .on_click(|_, window, cx| {
+ if let Some(multi_workspace) = window.root::<MultiWorkspace>().flatten() {
+ multi_workspace.update(cx, |multi_workspace, cx| {
+ multi_workspace.close_sidebar(window, cx);
+ });
+ }
+ })
})
}
+
+ fn render_sidebar_bottom_bar(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
+ let on_right = self.side(cx) == SidebarSide::Right;
+ let is_archive = matches!(self.view, SidebarView::Archive(..));
+ let action_buttons = h_flex()
+ .gap_1()
+ .child(
+ IconButton::new("archive", IconName::Archive)
+ .icon_size(IconSize::Small)
+ .toggle_state(is_archive)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action("Toggle Archived Threads", &ToggleArchive, cx)
+ })
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.toggle_archive(&ToggleArchive, window, cx);
+ })),
+ )
+ .child(self.render_recent_projects_button(cx));
+ let border_color = cx.theme().colors().border;
+ let toggle_button = self.render_sidebar_toggle_button(cx);
+
+ let bar = h_flex()
+ .p_1()
+ .gap_1()
+ .justify_between()
+ .border_t_1()
+ .border_color(border_color);
+
+ if on_right {
+ bar.child(action_buttons).child(toggle_button)
+ } else {
+ bar.child(toggle_button).child(action_buttons)
+ }
+ }
}
impl Sidebar {
@@ -3054,6 +3112,10 @@ impl WorkspaceSidebar for Sidebar {
matches!(self.view, SidebarView::ThreadList)
}
+ fn side(&self, cx: &App) -> SidebarSide {
+ AgentSettings::get_global(cx).sidebar_side()
+ }
+
fn prepare_for_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.selection = None;
cx.notify();
@@ -3108,7 +3170,8 @@ impl Render for Sidebar {
.h_full()
.w(self.width)
.bg(bg)
- .border_r_1()
+ .when(self.side(cx) == SidebarSide::Left, |el| el.border_r_1())
+ .when(self.side(cx) == SidebarSide::Right, |el| el.border_l_1())
.border_color(color.border)
.map(|this| match &self.view {
SidebarView::ThreadList => this
@@ -3140,35 +3203,7 @@ impl Render for Sidebar {
}),
SidebarView::Archive(archive_view) => this.child(archive_view.clone()),
})
- .child(
- h_flex()
- .p_1()
- .gap_1()
- .justify_between()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(self.render_sidebar_toggle_button(cx))
- .child(
- h_flex()
- .gap_1()
- .child(
- IconButton::new("archive", IconName::Archive)
- .icon_size(IconSize::Small)
- .toggle_state(matches!(self.view, SidebarView::Archive(..)))
- .tooltip(move |_, cx| {
- Tooltip::for_action(
- "Toggle Archived Threads",
- &ToggleArchive,
- cx,
- )
- })
- .on_click(cx.listener(|this, _, window, cx| {
- this.toggle_archive(&ToggleArchive, window, cx);
- })),
- )
- .child(self.render_recent_projects_button(cx)),
- ),
- )
+ .child(self.render_sidebar_bottom_bar(cx))
}
}
@@ -1654,7 +1654,7 @@ impl Panel for TerminalPanel {
}
fn activation_priority(&self) -> u32 {
- 1
+ 2
}
}
@@ -81,7 +81,8 @@ pub fn init(cx: &mut App) {
let Some(window) = window else {
return;
};
- let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
+ let multi_workspace = workspace.multi_workspace().cloned();
+ let item = cx.new(|cx| TitleBar::new("title-bar", workspace, multi_workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
workspace.register_action(|workspace, _: &SimulateUpdateAvailable, _window, cx| {
@@ -161,7 +162,18 @@ pub struct TitleBar {
impl Render for TitleBar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- self.sync_multi_workspace(window, cx);
+ if self.multi_workspace.is_none() {
+ if let Some(mw) = self
+ .workspace
+ .upgrade()
+ .and_then(|ws| ws.read(cx).multi_workspace().cloned())
+ {
+ self.multi_workspace = Some(mw.clone());
+ self.platform_titlebar.update(cx, |titlebar, _cx| {
+ titlebar.set_multi_workspace(mw);
+ });
+ }
+ }
let title_bar_settings = *TitleBarSettings::get_global(cx);
let button_layout = title_bar_settings.button_layout;
@@ -308,6 +320,7 @@ impl TitleBar {
pub fn new(
id: impl Into<ElementId>,
workspace: &Workspace,
+ multi_workspace: Option<WeakEntity<MultiWorkspace>>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -385,52 +398,19 @@ impl TitleBar {
});
let update_version = cx.new(|cx| UpdateVersion::new(cx));
- let platform_titlebar = cx.new(|cx| PlatformTitleBar::new(id, cx));
-
- // Set up observer to sync sidebar state from MultiWorkspace to PlatformTitleBar.
- {
- let platform_titlebar = platform_titlebar.clone();
- let window_handle = window.window_handle();
- cx.spawn(async move |this: WeakEntity<TitleBar>, cx| {
- let Some(multi_workspace_handle) = window_handle.downcast::<MultiWorkspace>()
- else {
- return;
- };
-
- let _ = cx.update(|cx| {
- let Ok(multi_workspace) = multi_workspace_handle.entity(cx) else {
- return;
- };
-
- let is_open = multi_workspace.read(cx).sidebar_open();
- platform_titlebar.update(cx, |titlebar, cx| {
- titlebar.set_workspace_sidebar_open(is_open, cx);
- });
-
- let platform_titlebar = platform_titlebar.clone();
- let subscription = cx.observe(&multi_workspace, move |mw, cx| {
- let is_open = mw.read(cx).sidebar_open();
- platform_titlebar.update(cx, |titlebar, cx| {
- titlebar.set_workspace_sidebar_open(is_open, cx);
- });
- });
-
- if let Some(this) = this.upgrade() {
- this.update(cx, |this, _| {
- this._subscriptions.push(subscription);
- this.multi_workspace = Some(multi_workspace.downgrade());
- });
- }
- });
- })
- .detach();
- }
+ let platform_titlebar = cx.new(|cx| {
+ let mut titlebar = PlatformTitleBar::new(id, cx);
+ if let Some(mw) = multi_workspace.clone() {
+ titlebar = titlebar.with_multi_workspace(mw);
+ }
+ titlebar
+ });
let mut this = Self {
platform_titlebar,
application_menu,
workspace: workspace.weak_handle(),
- multi_workspace: None,
+ multi_workspace,
project,
user_store,
client,
@@ -446,46 +426,6 @@ impl TitleBar {
this
}
- /// Used to update the title bar state in case the workspace has
- /// been moved to a new window through the threads sidebar.
- fn sync_multi_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
- let current = window
- .root::<MultiWorkspace>()
- .flatten()
- .map(|mw| mw.entity_id());
-
- let tracked = self
- .multi_workspace
- .as_ref()
- .and_then(|weak| weak.upgrade())
- .map(|mw| mw.entity_id());
-
- if current == tracked {
- return;
- }
-
- let Some(multi_workspace) = window.root::<MultiWorkspace>().flatten() else {
- self.multi_workspace = None;
- return;
- };
-
- let is_open = multi_workspace.read(cx).sidebar_open();
- self.platform_titlebar.update(cx, |titlebar, cx| {
- titlebar.set_workspace_sidebar_open(is_open, cx);
- });
-
- let platform_titlebar = self.platform_titlebar.clone();
- let subscription = cx.observe(&multi_workspace, move |_this, mw, cx| {
- let is_open = mw.read(cx).sidebar_open();
- platform_titlebar.update(cx, |titlebar, cx| {
- titlebar.set_workspace_sidebar_open(is_open, cx);
- });
- });
-
- self.multi_workspace = Some(multi_workspace.downgrade());
- self._subscriptions.push(subscription);
- }
-
fn worktree_count(&self, cx: &App) -> usize {
self.project.read(cx).visible_worktrees(cx).count()
}
@@ -777,7 +717,13 @@ impl TitleBar {
"Open Recent Project".to_string()
};
- let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open();
+ let is_sidebar_open = self
+ .multi_workspace
+ .as_ref()
+ .and_then(|mw| mw.upgrade())
+ .map(|mw| mw.read(cx).sidebar_open())
+ .unwrap_or(false)
+ && PlatformTitleBar::is_multi_workspace_enabled(cx);
let is_threads_list_view_active = self
.multi_workspace
@@ -27,6 +27,7 @@ test-support = [
[dependencies]
any_vec.workspace = true
+agent_settings.workspace = true
anyhow.workspace = true
async-recursion.workspace = true
client.workspace = true
@@ -69,6 +69,9 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
fn enabled(&self, _cx: &App) -> bool {
true
}
+ fn is_agent_panel(&self) -> bool {
+ false
+ }
}
pub trait PanelHandle: Send + Sync {
@@ -95,6 +98,7 @@ pub trait PanelHandle: Send + Sync {
fn to_any(&self) -> AnyView;
fn activation_priority(&self, cx: &App) -> u32;
fn enabled(&self, cx: &App) -> bool;
+ fn is_agent_panel(&self, cx: &App) -> bool;
fn move_to_next_position(&self, window: &mut Window, cx: &mut App) {
let current_position = self.position(window, cx);
let next_position = [
@@ -207,6 +211,10 @@ where
fn enabled(&self, cx: &App) -> bool {
self.read(cx).enabled(cx)
}
+
+ fn is_agent_panel(&self, cx: &App) -> bool {
+ self.read(cx).is_agent_panel()
+ }
}
impl From<&dyn PanelHandle> for AnyView {
@@ -720,6 +728,12 @@ impl Dock {
self.panel_entries.len()
}
+ pub fn has_agent_panel(&self, cx: &App) -> bool {
+ self.panel_entries
+ .iter()
+ .any(|entry| entry.panel.is_agent_panel(cx))
+ }
+
pub fn activate_panel(&mut self, panel_ix: usize, window: &mut Window, cx: &mut Context<Self>) {
if Some(panel_ix) != self.active_panel_index {
if let Some(active_panel) = self.active_panel_entry() {
@@ -9,6 +9,7 @@ use project::DisableAiSettings;
#[cfg(any(test, feature = "test-support"))]
use project::Project;
use settings::Settings;
+pub use settings::SidebarSide;
use std::future::Future;
use std::path::PathBuf;
use std::sync::Arc;
@@ -16,6 +17,10 @@ use ui::prelude::*;
use util::ResultExt;
use zed_actions::agents_sidebar::MoveWorkspaceToNewWindow;
+use agent_settings::AgentSettings;
+use settings::SidebarDockPosition;
+use ui::{ContextMenu, right_click_menu};
+
const SIDEBAR_RESIZE_HANDLE_SIZE: Pixels = px(6.0);
use crate::{
@@ -39,6 +44,47 @@ actions!(
]
);
+#[derive(Default)]
+pub struct SidebarRenderState {
+ pub open: bool,
+ pub side: SidebarSide,
+}
+
+pub fn sidebar_side_context_menu(
+ id: impl Into<ElementId>,
+ cx: &App,
+) -> ui::RightClickMenu<ContextMenu> {
+ let current_position = AgentSettings::get_global(cx).sidebar_side;
+ right_click_menu(id).menu(move |window, cx| {
+ let fs = <dyn fs::Fs>::global(cx);
+ ContextMenu::build(window, cx, move |mut menu, _, _cx| {
+ let positions: [(SidebarDockPosition, &str); 3] = [
+ (SidebarDockPosition::Left, "Left"),
+ (SidebarDockPosition::Right, "Right"),
+ (SidebarDockPosition::FollowAgent, "Follow Agent Panel"),
+ ];
+ for (position, label) in positions {
+ let fs = fs.clone();
+ menu = menu.toggleable_entry(
+ label,
+ position == current_position,
+ IconPosition::Start,
+ None,
+ move |_window, cx| {
+ settings::update_settings_file(fs.clone(), cx, move |settings, _cx| {
+ settings
+ .agent
+ .get_or_insert_default()
+ .set_sidebar_side(position);
+ });
+ },
+ );
+ }
+ menu
+ })
+ })
+}
+
pub enum MultiWorkspaceEvent {
ActiveWorkspaceChanged,
WorkspaceAdded(Entity<Workspace>),
@@ -49,6 +95,7 @@ pub trait Sidebar: Focusable + Render + Sized {
fn width(&self, cx: &App) -> Pixels;
fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
fn has_notifications(&self, cx: &App) -> bool;
+ fn side(&self, _cx: &App) -> SidebarSide;
fn is_threads_list_view_active(&self) -> bool {
true
@@ -68,6 +115,8 @@ pub trait SidebarHandle: 'static + Send + Sync {
fn entity_id(&self) -> EntityId;
fn is_threads_list_view_active(&self, cx: &App) -> bool;
+
+ fn side(&self, cx: &App) -> SidebarSide;
}
#[derive(Clone)]
@@ -116,6 +165,10 @@ impl<T: Sidebar> SidebarHandle for Entity<T> {
fn is_threads_list_view_active(&self, cx: &App) -> bool {
self.read(cx).is_threads_list_view_active()
}
+
+ fn side(&self, cx: &App) -> SidebarSide {
+ self.read(cx).side(cx)
+ }
}
pub struct MultiWorkspace {
@@ -132,6 +185,19 @@ pub struct MultiWorkspace {
impl EventEmitter<MultiWorkspaceEvent> for MultiWorkspace {}
impl MultiWorkspace {
+ pub fn sidebar_side(&self, cx: &App) -> SidebarSide {
+ self.sidebar
+ .as_ref()
+ .map_or(SidebarSide::Left, |s| s.side(cx))
+ }
+
+ pub fn sidebar_render_state(&self, cx: &App) -> SidebarRenderState {
+ SidebarRenderState {
+ open: self.sidebar_open() && self.multi_workspace_enabled(cx),
+ side: self.sidebar_side(cx),
+ }
+ }
+
pub fn new(workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let release_subscription = cx.on_release(|this: &mut MultiWorkspace, _cx| {
if let Some(task) = this._serialize_task.take() {
@@ -149,6 +215,10 @@ impl MultiWorkspace {
}
});
Self::subscribe_to_workspace(&workspace, cx);
+ let weak_self = cx.weak_entity();
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_multi_workspace(weak_self, cx);
+ });
Self {
window_id: window.window_handle().window_id(),
workspaces: vec![workspace],
@@ -167,20 +237,8 @@ impl MultiWorkspace {
pub fn register_sidebar<T: Sidebar>(&mut self, sidebar: Entity<T>, cx: &mut Context<Self>) {
self._subscriptions
- .push(cx.observe(&sidebar, |this, _, cx| {
- let has_notifications = this.sidebar_has_notifications(cx);
- let is_open = this.sidebar_open;
- let show_toggle = this.multi_workspace_enabled(cx);
- for workspace in &this.workspaces {
- workspace.update(cx, |workspace, cx| {
- workspace.set_workspace_sidebar_open(
- is_open,
- has_notifications,
- show_toggle,
- cx,
- );
- });
- }
+ .push(cx.observe(&sidebar, |_this, _, cx| {
+ cx.notify();
}));
self.sidebar = Some(Box::new(sidebar));
}
@@ -266,11 +324,8 @@ impl MultiWorkspace {
pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
self.sidebar_open = true;
let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
- let has_notifications = self.sidebar_has_notifications(cx);
- let show_toggle = self.multi_workspace_enabled(cx);
for workspace in &self.workspaces {
- workspace.update(cx, |workspace, cx| {
- workspace.set_workspace_sidebar_open(true, has_notifications, show_toggle, cx);
+ workspace.update(cx, |workspace, _cx| {
workspace.set_sidebar_focus_handle(sidebar_focus_handle.clone());
});
}
@@ -280,11 +335,8 @@ impl MultiWorkspace {
pub fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.sidebar_open = false;
- let has_notifications = self.sidebar_has_notifications(cx);
- let show_toggle = self.multi_workspace_enabled(cx);
for workspace in &self.workspaces {
- workspace.update(cx, |workspace, cx| {
- workspace.set_workspace_sidebar_open(false, has_notifications, show_toggle, cx);
+ workspace.update(cx, |workspace, _cx| {
workspace.set_sidebar_focus_handle(None);
});
}
@@ -381,13 +433,14 @@ impl MultiWorkspace {
} else {
if self.sidebar_open {
let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
- let has_notifications = self.sidebar_has_notifications(cx);
- let show_toggle = self.multi_workspace_enabled(cx);
- workspace.update(cx, |workspace, cx| {
- workspace.set_workspace_sidebar_open(true, has_notifications, show_toggle, cx);
+ workspace.update(cx, |workspace, _cx| {
workspace.set_sidebar_focus_handle(sidebar_focus_handle);
});
}
+ let weak_self = cx.weak_entity();
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_multi_workspace(weak_self, cx);
+ });
Self::subscribe_to_workspace(&workspace, cx);
self.workspaces.push(workspace.clone());
cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace));
@@ -767,6 +820,8 @@ impl MultiWorkspace {
impl Render for MultiWorkspace {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let multi_workspace_enabled = self.multi_workspace_enabled(cx);
+ let sidebar_side = self.sidebar_side(cx);
+ let sidebar_on_right = sidebar_side == SidebarSide::Right;
let sidebar: Option<AnyElement> = if multi_workspace_enabled && self.sidebar_open() {
self.sidebar.as_ref().map(|sidebar_handle| {
@@ -777,7 +832,12 @@ impl Render for MultiWorkspace {
div()
.id("sidebar-resize-handle")
.absolute()
- .right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
+ .when(!sidebar_on_right, |el| {
+ el.right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
+ })
+ .when(sidebar_on_right, |el| {
+ el.left(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
+ })
.top(px(0.))
.h_full()
.w(SIDEBAR_RESIZE_HANDLE_SIZE)
@@ -817,6 +877,12 @@ impl Render for MultiWorkspace {
None
};
+ let (left_sidebar, right_sidebar) = if sidebar_on_right {
+ (None, sidebar)
+ } else {
+ (sidebar, None)
+ };
+
let ui_font = theme::setup_ui_font(window, cx);
let text_color = cx.theme().colors().text;
@@ -855,16 +921,23 @@ impl Render for MultiWorkspace {
self.sidebar_open() && self.multi_workspace_enabled(cx),
|this| {
this.on_drag_move(cx.listener(
- |this: &mut Self, e: &DragMoveEvent<DraggedSidebar>, _window, cx| {
+ move |this: &mut Self,
+ e: &DragMoveEvent<DraggedSidebar>,
+ window,
+ cx| {
if let Some(sidebar) = &this.sidebar {
- let new_width = e.event.position.x;
+ let new_width = if sidebar_on_right {
+ window.bounds().size.width - e.event.position.x
+ } else {
+ e.event.position.x
+ };
sidebar.set_width(Some(new_width), cx);
}
},
))
- .children(sidebar)
},
)
+ .children(left_sidebar)
.child(
div()
.flex()
@@ -873,11 +946,13 @@ impl Render for MultiWorkspace {
.overflow_hidden()
.child(self.workspace().clone()),
)
+ .children(right_sidebar)
.child(self.workspace().read(cx).modal_layer.clone()),
window,
cx,
Tiling {
- left: multi_workspace_enabled && self.sidebar_open(),
+ left: !sidebar_on_right && multi_workspace_enabled && self.sidebar_open(),
+ right: sidebar_on_right && multi_workspace_enabled && self.sidebar_open(),
..Tiling::default()
},
)
@@ -1,7 +1,10 @@
-use crate::{ItemHandle, MultiWorkspace, Pane, ToggleWorkspaceSidebar};
+use crate::{
+ ItemHandle, MultiWorkspace, Pane, SidebarSide, ToggleWorkspaceSidebar,
+ sidebar_side_context_menu,
+};
use gpui::{
- AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
- Subscription, Window,
+ AnyView, App, Context, Corner, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
+ Subscription, WeakEntity, Window,
};
use std::any::TypeId;
use theme::CLIENT_SIDE_DECORATION_ROUNDING;
@@ -29,18 +32,45 @@ trait StatusItemViewHandle: Send {
fn item_type(&self) -> TypeId;
}
+#[derive(Default)]
+struct SidebarStatus {
+ open: bool,
+ side: SidebarSide,
+ has_notifications: bool,
+ show_toggle: bool,
+}
+
+impl SidebarStatus {
+ fn query(multi_workspace: &Option<WeakEntity<MultiWorkspace>>, cx: &App) -> Self {
+ multi_workspace
+ .as_ref()
+ .and_then(|mw| mw.upgrade())
+ .map(|mw| {
+ let mw = mw.read(cx);
+ let enabled = mw.multi_workspace_enabled(cx);
+ Self {
+ open: mw.sidebar_open() && enabled,
+ side: mw.sidebar_side(cx),
+ has_notifications: mw.sidebar_has_notifications(cx),
+ show_toggle: enabled,
+ }
+ })
+ .unwrap_or_default()
+ }
+}
+
pub struct StatusBar {
left_items: Vec<Box<dyn StatusItemViewHandle>>,
right_items: Vec<Box<dyn StatusItemViewHandle>>,
active_pane: Entity<Pane>,
+ multi_workspace: Option<WeakEntity<MultiWorkspace>>,
_observe_active_pane: Subscription,
- workspace_sidebar_open: bool,
- sidebar_has_notifications: bool,
- show_sidebar_toggle: bool,
}
impl Render for StatusBar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ let sidebar = SidebarStatus::query(&self.multi_workspace, cx);
+
h_flex()
.w_full()
.justify_between()
@@ -50,11 +80,14 @@ impl Render for StatusBar {
.map(|el| match window.window_decorations() {
Decorations::Server => el,
Decorations::Client { tiling, .. } => el
- .when(!(tiling.bottom || tiling.right), |el| {
- el.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
- })
.when(
- !(tiling.bottom || tiling.left) && !self.workspace_sidebar_open,
+ !(tiling.bottom || tiling.right)
+ && !(sidebar.open && sidebar.side == SidebarSide::Right),
+ |el| el.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING),
+ )
+ .when(
+ !(tiling.bottom || tiling.left)
+ && !(sidebar.open && sidebar.side == SidebarSide::Left),
|el| el.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING),
)
// This border is to avoid a transparent gap in the rounded corners
@@ -62,44 +95,77 @@ impl Render for StatusBar {
.border_b(px(1.0))
.border_color(cx.theme().colors().status_bar_background),
})
- .child(self.render_left_tools(cx))
- .child(self.render_right_tools())
+ .child(self.render_left_tools(&sidebar, cx))
+ .child(self.render_right_tools(&sidebar, cx))
}
}
impl StatusBar {
- fn render_left_tools(&self, cx: &mut Context<Self>) -> impl IntoElement {
+ fn render_left_tools(
+ &self,
+ sidebar: &SidebarStatus,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
h_flex()
.gap_1()
.min_w_0()
.overflow_x_hidden()
.when(
- self.show_sidebar_toggle && !self.workspace_sidebar_open,
- |this| this.child(self.render_sidebar_toggle(cx)),
+ sidebar.show_toggle && !sidebar.open && sidebar.side == SidebarSide::Left,
+ |this| this.child(self.render_sidebar_toggle(sidebar, cx)),
)
.children(self.left_items.iter().map(|item| item.to_any()))
}
- fn render_right_tools(&self) -> impl IntoElement {
+ fn render_right_tools(
+ &self,
+ sidebar: &SidebarStatus,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
h_flex()
.flex_shrink_0()
.gap_1()
.overflow_x_hidden()
.children(self.right_items.iter().rev().map(|item| item.to_any()))
+ .when(
+ sidebar.show_toggle && !sidebar.open && sidebar.side == SidebarSide::Right,
+ |this| this.child(self.render_sidebar_toggle(sidebar, cx)),
+ )
}
- fn render_sidebar_toggle(&self, cx: &mut Context<Self>) -> impl IntoElement {
- h_flex()
- .gap_0p5()
- .child(
+ fn render_sidebar_toggle(
+ &self,
+ sidebar: &SidebarStatus,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
+ let on_right = sidebar.side == SidebarSide::Right;
+ let has_notifications = sidebar.has_notifications;
+ let indicator_border = cx.theme().colors().status_bar_background;
+
+ let toggle = sidebar_side_context_menu("sidebar-status-toggle-menu", cx)
+ .anchor(if on_right {
+ Corner::BottomRight
+ } else {
+ Corner::BottomLeft
+ })
+ .attach(if on_right {
+ Corner::TopRight
+ } else {
+ Corner::TopLeft
+ })
+ .trigger(move |_is_active, _window, _cx| {
IconButton::new(
"toggle-workspace-sidebar",
- IconName::ThreadsSidebarLeftClosed,
+ if on_right {
+ IconName::ThreadsSidebarRightClosed
+ } else {
+ IconName::ThreadsSidebarLeftClosed
+ },
)
.icon_size(IconSize::Small)
- .when(self.sidebar_has_notifications, |this| {
+ .when(has_notifications, |this| {
this.indicator(Indicator::dot().color(Color::Accent))
- .indicator_border_color(Some(cx.theme().colors().status_bar_background))
+ .indicator_border_color(Some(indicator_border))
})
.tooltip(move |_, cx| {
Tooltip::for_action("Open Threads Sidebar", &ToggleWorkspaceSidebar, cx)
@@ -110,41 +176,47 @@ impl StatusBar {
multi_workspace.toggle_sidebar(window, cx);
});
}
- }),
- )
- .child(Divider::vertical().color(ui::DividerColor::Border))
+ })
+ });
+
+ h_flex()
+ .gap_0p5()
+ .when(on_right, |this| {
+ this.child(Divider::vertical().color(ui::DividerColor::Border))
+ })
+ .child(toggle)
+ .when(!on_right, |this| {
+ this.child(Divider::vertical().color(ui::DividerColor::Border))
+ })
}
}
impl StatusBar {
- pub fn new(active_pane: &Entity<Pane>, window: &mut Window, cx: &mut Context<Self>) -> Self {
+ pub fn new(
+ active_pane: &Entity<Pane>,
+ multi_workspace: Option<WeakEntity<MultiWorkspace>>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Self {
let mut this = Self {
left_items: Default::default(),
right_items: Default::default(),
active_pane: active_pane.clone(),
+ multi_workspace,
_observe_active_pane: cx.observe_in(active_pane, window, |this, _, window, cx| {
this.update_active_pane_item(window, cx)
}),
- workspace_sidebar_open: false,
- sidebar_has_notifications: false,
- show_sidebar_toggle: false,
};
this.update_active_pane_item(window, cx);
this
}
- pub fn set_workspace_sidebar_open(&mut self, open: bool, cx: &mut Context<Self>) {
- self.workspace_sidebar_open = open;
- cx.notify();
- }
-
- pub fn set_sidebar_has_notifications(&mut self, has: bool, cx: &mut Context<Self>) {
- self.sidebar_has_notifications = has;
- cx.notify();
- }
-
- pub fn set_show_sidebar_toggle(&mut self, show: bool, cx: &mut Context<Self>) {
- self.show_sidebar_toggle = show;
+ pub fn set_multi_workspace(
+ &mut self,
+ multi_workspace: WeakEntity<MultiWorkspace>,
+ cx: &mut Context<Self>,
+ ) {
+ self.multi_workspace = Some(multi_workspace);
cx.notify();
}
@@ -29,7 +29,7 @@ pub use dock::Panel;
pub use multi_workspace::{
CloseWorkspaceSidebar, DraggedSidebar, FocusWorkspaceSidebar, MultiWorkspace,
MultiWorkspaceEvent, NextWorkspace, PreviousWorkspace, Sidebar, SidebarHandle,
- ToggleWorkspaceSidebar,
+ SidebarRenderState, SidebarSide, ToggleWorkspaceSidebar, sidebar_side_context_menu,
};
pub use path_list::{PathList, SerializedPathList};
pub use toast_layer::{ToastAction, ToastLayer, ToastView};
@@ -1342,6 +1342,7 @@ pub struct Workspace {
removing: bool,
_panels_task: Option<Task<Result<()>>>,
sidebar_focus_handle: Option<FocusHandle>,
+ multi_workspace: Option<WeakEntity<MultiWorkspace>>,
}
impl EventEmitter<Event> for Workspace {}
@@ -1626,8 +1627,13 @@ impl Workspace {
let left_dock_buttons = cx.new(|cx| PanelButtons::new(left_dock.clone(), cx));
let bottom_dock_buttons = cx.new(|cx| PanelButtons::new(bottom_dock.clone(), cx));
let right_dock_buttons = cx.new(|cx| PanelButtons::new(right_dock.clone(), cx));
+ let multi_workspace = window
+ .root::<MultiWorkspace>()
+ .flatten()
+ .map(|mw| mw.downgrade());
let status_bar = cx.new(|cx| {
- let mut status_bar = StatusBar::new(¢er_pane.clone(), window, cx);
+ let mut status_bar =
+ StatusBar::new(¢er_pane.clone(), multi_workspace.clone(), window, cx);
status_bar.add_left_item(left_dock_buttons, window, cx);
status_bar.add_right_item(right_dock_buttons, window, cx);
status_bar.add_right_item(bottom_dock_buttons, window, cx);
@@ -1754,6 +1760,7 @@ impl Workspace {
last_open_dock_positions: Vec::new(),
removing: false,
sidebar_focus_handle: None,
+ multi_workspace,
}
}
@@ -2127,6 +2134,13 @@ impl Workspace {
}
}
+ pub fn agent_panel_position(&self, cx: &App) -> Option<DockPosition> {
+ self.all_docks().into_iter().find_map(|dock| {
+ let dock = dock.read(cx);
+ dock.has_agent_panel(cx).then_some(dock.position())
+ })
+ }
+
pub fn panel_size_state<T: Panel>(&self, cx: &App) -> Option<dock::PanelSizeState> {
self.all_docks().into_iter().find_map(|dock| {
let dock = dock.read(cx);
@@ -2327,20 +2341,6 @@ impl Workspace {
&self.status_bar
}
- pub fn set_workspace_sidebar_open(
- &self,
- open: bool,
- has_notifications: bool,
- show_toggle: bool,
- cx: &mut App,
- ) {
- self.status_bar.update(cx, |status_bar, cx| {
- status_bar.set_workspace_sidebar_open(open, cx);
- status_bar.set_sidebar_has_notifications(has_notifications, cx);
- status_bar.set_show_sidebar_toggle(show_toggle, cx);
- });
- }
-
pub fn set_sidebar_focus_handle(&mut self, handle: Option<FocusHandle>) {
self.sidebar_focus_handle = handle;
}
@@ -2349,6 +2349,21 @@ impl Workspace {
StatusBarSettings::get_global(cx).show
}
+ pub fn multi_workspace(&self) -> Option<&WeakEntity<MultiWorkspace>> {
+ self.multi_workspace.as_ref()
+ }
+
+ pub fn set_multi_workspace(
+ &mut self,
+ multi_workspace: WeakEntity<MultiWorkspace>,
+ cx: &mut App,
+ ) {
+ self.status_bar.update(cx, |status_bar, cx| {
+ status_bar.set_multi_workspace(multi_workspace.clone(), cx);
+ });
+ self.multi_workspace = Some(multi_workspace);
+ }
+
pub fn app_state(&self) -> &Arc<AppState> {
&self.app_state
}