Skip to content

Commit aca5209

Browse files
authored
Make the agent panel have a flexible width (zed-industries#52276)
Release Notes: - The agent panel now has a flexible width, similar to the center panes of the workspace.
1 parent 23ac4fd commit aca5209

15 files changed

Lines changed: 1022 additions & 361 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/agent_ui/src/agent_panel.rs

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ fn read_legacy_serialized_panel(kvp: &KeyValueStore) -> Option<SerializedAgentPa
131131

132132
#[derive(Serialize, Deserialize, Debug)]
133133
struct SerializedAgentPanel {
134-
width: Option<Pixels>,
135134
selected_agent: Option<AgentType>,
136135
#[serde(default)]
137136
last_active_thread: Option<SerializedActiveThread>,
@@ -743,8 +742,6 @@ pub struct AgentPanel {
743742
agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
744743
agent_navigation_menu: Option<Entity<ContextMenu>>,
745744
_extension_subscription: Option<Subscription>,
746-
width: Option<Pixels>,
747-
height: Option<Pixels>,
748745
zoomed: bool,
749746
pending_serialization: Option<Task<Result<()>>>,
750747
onboarding: Entity<AgentPanelOnboarding>,
@@ -766,7 +763,6 @@ impl AgentPanel {
766763
return;
767764
};
768765

769-
let width = self.width;
770766
let selected_agent_type = self.selected_agent_type.clone();
771767
let start_thread_in = Some(self.start_thread_in);
772768

@@ -787,7 +783,6 @@ impl AgentPanel {
787783
save_serialized_panel(
788784
workspace_id,
789785
SerializedAgentPanel {
790-
width,
791786
selected_agent: Some(selected_agent_type),
792787
last_active_thread,
793788
start_thread_in,
@@ -876,7 +871,6 @@ impl AgentPanel {
876871

877872
if let Some(serialized_panel) = &serialized_panel {
878873
panel.update(cx, |panel, cx| {
879-
panel.width = serialized_panel.width.map(|w| w.round());
880874
if let Some(selected_agent) = serialized_panel.selected_agent.clone() {
881875
panel.selected_agent_type = selected_agent;
882876
}
@@ -1079,8 +1073,6 @@ impl AgentPanel {
10791073
agent_navigation_menu_handle: PopoverMenuHandle::default(),
10801074
agent_navigation_menu: None,
10811075
_extension_subscription: extension_subscription,
1082-
width: None,
1083-
height: None,
10841076
zoomed: false,
10851077
pending_serialization: None,
10861078
onboarding,
@@ -3150,23 +3142,16 @@ impl Panel for AgentPanel {
31503142
});
31513143
}
31523144

3153-
fn size(&self, window: &Window, cx: &App) -> Pixels {
3145+
fn default_size(&self, window: &Window, cx: &App) -> Pixels {
31543146
let settings = AgentSettings::get_global(cx);
31553147
match self.position(window, cx) {
3156-
DockPosition::Left | DockPosition::Right => {
3157-
self.width.unwrap_or(settings.default_width)
3158-
}
3159-
DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
3148+
DockPosition::Left | DockPosition::Right => settings.default_width,
3149+
DockPosition::Bottom => settings.default_height,
31603150
}
31613151
}
31623152

3163-
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
3164-
match self.position(window, cx) {
3165-
DockPosition::Left | DockPosition::Right => self.width = size,
3166-
DockPosition::Bottom => self.height = size,
3167-
}
3168-
self.serialize(cx);
3169-
cx.notify();
3153+
fn supports_flexible_size(&self, _window: &Window, _cx: &App) -> bool {
3154+
true
31703155
}
31713156

31723157
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
@@ -5055,16 +5040,12 @@ mod tests {
50555040

50565041
let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
50575042

5058-
// --- Set up workspace A: width=300, with an active thread ---
5043+
// --- Set up workspace A: with an active thread ---
50595044
let panel_a = workspace_a.update_in(cx, |workspace, window, cx| {
50605045
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_a.clone(), cx));
50615046
cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
50625047
});
50635048

5064-
panel_a.update(cx, |panel, _cx| {
5065-
panel.width = Some(px(300.0));
5066-
});
5067-
50685049
panel_a.update_in(cx, |panel, window, cx| {
50695050
panel.open_external_thread_with_server(
50705051
Rc::new(StubAgentServer::default_response()),
@@ -5084,14 +5065,13 @@ mod tests {
50845065

50855066
let agent_type_a = panel_a.read_with(cx, |panel, _cx| panel.selected_agent_type.clone());
50865067

5087-
// --- Set up workspace B: ClaudeCode, width=400, no active thread ---
5068+
// --- Set up workspace B: ClaudeCode, no active thread ---
50885069
let panel_b = workspace_b.update_in(cx, |workspace, window, cx| {
50895070
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_b.clone(), cx));
50905071
cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
50915072
});
50925073

50935074
panel_b.update(cx, |panel, _cx| {
5094-
panel.width = Some(px(400.0));
50955075
panel.selected_agent_type = AgentType::Custom {
50965076
id: "claude-acp".into(),
50975077
};
@@ -5117,13 +5097,8 @@ mod tests {
51175097
.expect("panel B load should succeed");
51185098
cx.run_until_parked();
51195099

5120-
// Workspace A should restore its thread, width, and agent type
5100+
// Workspace A should restore its thread and agent type
51215101
loaded_a.read_with(cx, |panel, _cx| {
5122-
assert_eq!(
5123-
panel.width,
5124-
Some(px(300.0)),
5125-
"workspace A width should be restored"
5126-
);
51275102
assert_eq!(
51285103
panel.selected_agent_type, agent_type_a,
51295104
"workspace A agent type should be restored"
@@ -5134,13 +5109,8 @@ mod tests {
51345109
);
51355110
});
51365111

5137-
// Workspace B should restore its own width and agent type, with no thread
5112+
// Workspace B should restore its own agent type, with no thread
51385113
loaded_b.read_with(cx, |panel, _cx| {
5139-
assert_eq!(
5140-
panel.width,
5141-
Some(px(400.0)),
5142-
"workspace B width should be restored"
5143-
);
51445114
assert_eq!(
51455115
panel.selected_agent_type,
51465116
AgentType::Custom {

crates/collab_ui/src/collab_panel.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ impl ChannelEditingState {
237237
}
238238

239239
pub struct CollabPanel {
240-
width: Option<Pixels>,
241240
fs: Arc<dyn Fs>,
242241
focus_handle: FocusHandle,
243242
channel_clipboard: Option<ChannelMoveClipboard>,
@@ -263,7 +262,6 @@ pub struct CollabPanel {
263262

264263
#[derive(Serialize, Deserialize)]
265264
struct SerializedCollabPanel {
266-
width: Option<Pixels>,
267265
collapsed_channels: Option<Vec<u64>>,
268266
}
269267

@@ -371,7 +369,6 @@ impl CollabPanel {
371369
.detach();
372370

373371
let mut this = Self {
374-
width: None,
375372
focus_handle: cx.focus_handle(),
376373
channel_clipboard: None,
377374
fs: workspace.app_state().fs.clone(),
@@ -461,7 +458,6 @@ impl CollabPanel {
461458
let panel = CollabPanel::new(workspace, window, cx);
462459
if let Some(serialized_panel) = serialized_panel {
463460
panel.update(cx, |panel, cx| {
464-
panel.width = serialized_panel.width.map(|w| w.round());
465461
panel.collapsed_channels = serialized_panel
466462
.collapsed_channels
467463
.unwrap_or_else(Vec::new)
@@ -492,19 +488,17 @@ impl CollabPanel {
492488
else {
493489
return;
494490
};
495-
let width = self.width;
496-
let collapsed_channels = self.collapsed_channels.clone();
491+
let collapsed_channels = if self.collapsed_channels.is_empty() {
492+
None
493+
} else {
494+
Some(self.collapsed_channels.iter().map(|id| id.0).collect())
495+
};
497496
let kvp = KeyValueStore::global(cx);
498497
self.pending_serialization = cx.background_spawn(
499498
async move {
500499
kvp.write_kvp(
501500
serialization_key,
502-
serde_json::to_string(&SerializedCollabPanel {
503-
width,
504-
collapsed_channels: Some(
505-
collapsed_channels.iter().map(|cid| cid.0).collect(),
506-
),
507-
})?,
501+
serde_json::to_string(&SerializedCollabPanel { collapsed_channels })?,
508502
)
509503
.await?;
510504
anyhow::Ok(())
@@ -2915,7 +2909,16 @@ impl CollabPanel {
29152909
Some(result)
29162910
};
29172911

2918-
let width = self.width.unwrap_or(px(240.));
2912+
let width = self
2913+
.workspace
2914+
.read_with(cx, |workspace, cx| {
2915+
workspace
2916+
.panel_size_state::<Self>(cx)
2917+
.and_then(|size_state| size_state.size)
2918+
})
2919+
.ok()
2920+
.flatten()
2921+
.unwrap_or(px(240.));
29192922
let root_id = channel.root_id();
29202923

29212924
div()
@@ -3193,17 +3196,8 @@ impl Panel for CollabPanel {
31933196
});
31943197
}
31953198

3196-
fn size(&self, _window: &Window, cx: &App) -> Pixels {
3197-
self.width
3198-
.unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
3199-
}
3200-
3201-
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
3202-
self.width = size;
3203-
cx.notify();
3204-
cx.defer_in(window, |this, _, cx| {
3205-
this.serialize(cx);
3206-
});
3199+
fn default_size(&self, _window: &Window, cx: &App) -> Pixels {
3200+
CollaborationPanelSettings::get_global(cx).default_width
32073201
}
32083202

32093203
fn icon(&self, _window: &Window, cx: &App) -> Option<ui::IconName> {

crates/collab_ui/src/notification_panel.rs

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use anyhow::Result;
33
use channel::ChannelStore;
44
use client::{ChannelId, Client, Notification, User, UserStore};
55
use collections::HashMap;
6-
use db::kvp::KeyValueStore;
76
use futures::StreamExt;
87
use gpui::{
98
AnyElement, App, AsyncWindowContext, ClickEvent, Context, DismissEvent, Element, Entity,
@@ -14,14 +13,14 @@ use gpui::{
1413
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
1514
use project::Fs;
1615
use rpc::proto;
17-
use serde::{Deserialize, Serialize};
16+
1817
use settings::{Settings, SettingsStore};
1918
use std::{sync::Arc, time::Duration};
2019
use time::{OffsetDateTime, UtcOffset};
2120
use ui::{
2221
Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex,
2322
};
24-
use util::{ResultExt, TryFutureExt};
23+
use util::ResultExt;
2524
use workspace::notifications::{
2625
Notification as WorkspaceNotification, NotificationId, SuppressEvent,
2726
};
@@ -41,10 +40,8 @@ pub struct NotificationPanel {
4140
channel_store: Entity<ChannelStore>,
4241
notification_store: Entity<NotificationStore>,
4342
fs: Arc<dyn Fs>,
44-
width: Option<Pixels>,
4543
active: bool,
4644
notification_list: ListState,
47-
pending_serialization: Task<Option<()>>,
4845
subscriptions: Vec<gpui::Subscription>,
4946
workspace: WeakEntity<Workspace>,
5047
current_notification_toast: Option<(u64, Task<()>)>,
@@ -54,11 +51,6 @@ pub struct NotificationPanel {
5451
unseen_notifications: Vec<NotificationEntry>,
5552
}
5653

57-
#[derive(Serialize, Deserialize)]
58-
struct SerializedNotificationPanel {
59-
width: Option<Pixels>,
60-
}
61-
6254
#[derive(Debug)]
6355
pub enum Event {
6456
DockPositionChanged,
@@ -146,15 +138,13 @@ impl NotificationPanel {
146138
channel_store: ChannelStore::global(cx),
147139
notification_store: NotificationStore::global(cx),
148140
notification_list,
149-
pending_serialization: Task::ready(None),
150141
workspace: workspace_handle,
151142
focus_handle: cx.focus_handle(),
143+
subscriptions: Default::default(),
152144
current_notification_toast: None,
153-
subscriptions: Vec::new(),
154145
active: false,
155-
mark_as_read_tasks: HashMap::default(),
156-
width: None,
157-
unseen_notifications: Vec::new(),
146+
mark_as_read_tasks: Default::default(),
147+
unseen_notifications: Default::default(),
158148
};
159149

160150
let mut old_dock_position = this.position(window, cx);
@@ -186,43 +176,10 @@ impl NotificationPanel {
186176
cx: AsyncWindowContext,
187177
) -> Task<Result<Entity<Self>>> {
188178
cx.spawn(async move |cx| {
189-
let kvp = cx.update(|_, cx| KeyValueStore::global(cx))?;
190-
let serialized_panel =
191-
if let Some(panel) = kvp.read_kvp(NOTIFICATION_PANEL_KEY).log_err().flatten() {
192-
Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?)
193-
} else {
194-
None
195-
};
196-
197-
workspace.update_in(cx, |workspace, window, cx| {
198-
let panel = Self::new(workspace, window, cx);
199-
if let Some(serialized_panel) = serialized_panel {
200-
panel.update(cx, |panel, cx| {
201-
panel.width = serialized_panel.width.map(|w| w.round());
202-
cx.notify();
203-
});
204-
}
205-
panel
206-
})
179+
workspace.update_in(cx, |workspace, window, cx| Self::new(workspace, window, cx))
207180
})
208181
}
209182

210-
fn serialize(&mut self, cx: &mut Context<Self>) {
211-
let width = self.width;
212-
let kvp = KeyValueStore::global(cx);
213-
self.pending_serialization = cx.background_spawn(
214-
async move {
215-
kvp.write_kvp(
216-
NOTIFICATION_PANEL_KEY.into(),
217-
serde_json::to_string(&SerializedNotificationPanel { width })?,
218-
)
219-
.await?;
220-
anyhow::Ok(())
221-
}
222-
.log_err(),
223-
);
224-
}
225-
226183
fn render_notification(
227184
&mut self,
228185
ix: usize,
@@ -632,15 +589,8 @@ impl Panel for NotificationPanel {
632589
});
633590
}
634591

635-
fn size(&self, _: &Window, cx: &App) -> Pixels {
636-
self.width
637-
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
638-
}
639-
640-
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
641-
self.width = size;
642-
self.serialize(cx);
643-
cx.notify();
592+
fn default_size(&self, _: &Window, cx: &App) -> Pixels {
593+
NotificationPanelSettings::get_global(cx).default_width
644594
}
645595

646596
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut Context<Self>) {

0 commit comments

Comments
 (0)