Compare commits

...

3 Commits

Author SHA1 Message Date
Mikayla Maki
e984c18257 Render the panelet 2025-11-04 16:13:15 -08:00
Mikayla Maki
3166569c9a Add the "open aside" button
co-authored-by: Nathan <nathan@zed.dev>
2025-11-04 15:21:30 -08:00
Mikayla Maki
440bb2ff04 Add basic agents view
Co-authored-by: Nathan Sobo <nathan@zed.dev>
2025-11-04 14:10:47 -08:00
8 changed files with 332 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ mod agent_configuration;
mod agent_diff;
mod agent_model_selector;
mod agent_panel;
pub mod agents_panel;
mod buffer_codegen;
mod context;
mod context_picker;
@@ -249,6 +250,7 @@ pub fn init(
cx: &mut App,
) {
AgentSettings::register(cx);
agents_panel::init(cx);
assistant_text_thread::init(client.clone(), cx);
rules_library::init(cx);

View File

@@ -0,0 +1,181 @@
use gpui::{EventEmitter, Focusable, actions};
use ui::{
App, Context, IconName, IntoElement, Label, LabelCommon as _, LabelSize, ListItem,
ListItemSpacing, ParentElement, Render, RenderOnce, Styled, Toggleable as _, Window, div,
h_flex, px,
};
use workspace::{Panel, Workspace, dock::PanelEvent};
actions!(
agents,
[
/// Toggle the visibility of the agents panel.
ToggleAgentsPanel
]
);
pub fn init(cx: &mut App) {
// init_settings(cx);
cx.observe_new(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, _: &ToggleAgentsPanel, window, cx| {
workspace.toggle_panel_focus::<AgentsPanel>(window, cx);
});
})
.detach();
}
pub struct AgentsPanel {
focus_handle: gpui::FocusHandle,
}
impl AgentsPanel {
pub fn new(cx: &mut ui::Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
Self { focus_handle }
}
}
impl Panel for AgentsPanel {
fn persistent_name() -> &'static str {
"AgentsPanel"
}
fn panel_key() -> &'static str {
"AgentsPanel"
}
fn position(&self, window: &ui::Window, cx: &ui::App) -> workspace::dock::DockPosition {
workspace::dock::DockPosition::Left
}
fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
match position {
workspace::dock::DockPosition::Left | workspace::dock::DockPosition::Right => true,
workspace::dock::DockPosition::Bottom => false,
}
}
fn set_position(
&mut self,
_position: workspace::dock::DockPosition,
_window: &mut ui::Window,
_cx: &mut ui::Context<Self>,
) {
// TODO!
}
fn size(&self, _window: &ui::Window, _cx: &ui::App) -> ui::Pixels {
// TODO!
px(300.0)
}
fn set_size(
&mut self,
_size: Option<ui::Pixels>,
_window: &mut ui::Window,
_cx: &mut ui::Context<Self>,
) {
// TODO!
}
fn icon(&self, _window: &ui::Window, _cx: &ui::App) -> Option<ui::IconName> {
//todo!
Some(IconName::ZedAssistant)
}
fn icon_tooltip(&self, _window: &ui::Window, _cx: &ui::App) -> Option<&'static str> {
//todo!
Some("Agents panel")
}
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleAgentsPanel)
}
fn activation_priority(&self) -> u32 {
1
}
fn starts_open(&self, _window: &Window, _cx: &App) -> bool {
true
}
fn enabled(&self, _cx: &App) -> bool {
true
}
}
impl EventEmitter<PanelEvent> for AgentsPanel {}
impl Focusable for AgentsPanel {
fn focus_handle(&self, _cx: &ui::App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
#[derive(IntoElement)]
struct AgentThreadSummary {
title: gpui::SharedString,
worktree_branch: Option<gpui::SharedString>,
diff: AgentThreadDiff,
}
#[derive(IntoElement)]
struct AgentThreadDiff {
removed: usize,
modified: usize,
added: usize,
}
impl Render for AgentsPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let agent_threads = vec![
AgentThreadSummary {
title: "Building the agents panel".into(),
worktree_branch: Some("new-threads-pane".into()),
diff: AgentThreadDiff {
removed: 0,
modified: 0,
added: 1,
},
},
AgentThreadSummary {
title: "Integrate Delta DB".into(),
worktree_branch: Some("integrate-deltadb".into()),
diff: AgentThreadDiff {
removed: 2,
modified: 10,
added: 3,
},
},
];
div().size_full().children(agent_threads)
}
}
impl RenderOnce for AgentThreadSummary {
fn render(self, _window: &mut Window, _cx: &mut ui::App) -> impl IntoElement {
ListItem::new("list-item")
.rounded()
.spacing(ListItemSpacing::Sparse)
.start_slot(
h_flex()
.w_full()
.gap_2()
.justify_between()
.child(Label::new(self.title).size(LabelSize::Default).truncate())
.children(
self.worktree_branch
.map(|branch| Label::new(branch).size(LabelSize::Small).truncate()),
)
.child(self.diff),
)
}
}
impl RenderOnce for AgentThreadDiff {
fn render(self, _window: &mut Window, _cx: &mut ui::App) -> impl IntoElement {
Label::new(format!("{}:{}:{}", self.added, self.modified, self.removed))
}
}

View File

@@ -10,6 +10,7 @@ pub struct TabBar {
start_children: SmallVec<[AnyElement; 2]>,
children: SmallVec<[AnyElement; 2]>,
end_children: SmallVec<[AnyElement; 2]>,
pre_end_children: SmallVec<[AnyElement; 2]>,
scroll_handle: Option<ScrollHandle>,
}
@@ -20,6 +21,7 @@ impl TabBar {
start_children: SmallVec::new(),
children: SmallVec::new(),
end_children: SmallVec::new(),
pre_end_children: SmallVec::new(),
scroll_handle: None,
}
}
@@ -70,6 +72,15 @@ impl TabBar {
self
}
pub fn pre_end_child(mut self, end_child: impl IntoElement) -> Self
where
Self: Sized,
{
self.pre_end_children
.push(end_child.into_element().into_any());
self
}
pub fn end_children(mut self, end_children: impl IntoIterator<Item = impl IntoElement>) -> Self
where
Self: Sized,
@@ -137,18 +148,31 @@ impl RenderOnce for TabBar {
.children(self.children),
),
)
.when(!self.end_children.is_empty(), |this| {
this.child(
h_flex()
.flex_none()
.gap(DynamicSpacing::Base04.rems(cx))
.px(DynamicSpacing::Base06.rems(cx))
.border_b_1()
.border_l_1()
.border_color(cx.theme().colors().border)
.children(self.end_children),
)
})
.when(
!self.end_children.is_empty() || !self.pre_end_children.is_empty(),
|this| {
this.child(
h_flex()
.flex_none()
.gap(DynamicSpacing::Base04.rems(cx))
.px(DynamicSpacing::Base06.rems(cx))
.children(self.pre_end_children)
.border_color(cx.theme().colors().border)
.border_b_1()
.when(!self.end_children.is_empty(), |div| {
div.child(
h_flex()
.flex_none()
.pl(DynamicSpacing::Base04.rems(cx))
.gap(DynamicSpacing::Base04.rems(cx))
.border_l_1()
.border_color(cx.theme().colors().border)
.children(self.end_children),
)
}),
)
},
)
}
}

View File

@@ -13,6 +13,7 @@ use settings::SettingsStore;
use std::sync::Arc;
use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex};
use ui::{prelude::*, right_click_menu};
use util::ResultExt as _;
pub(crate) const RESIZE_HANDLE_SIZE: Pixels = px(6.);
@@ -882,7 +883,13 @@ impl Render for PanelButtons {
.enumerate()
.filter_map(|(i, entry)| {
let icon = entry.panel.icon(window, cx)?;
let icon_tooltip = entry.panel.icon_tooltip(window, cx)?;
let icon_tooltip = entry
.panel
.icon_tooltip(window, cx)
.ok_or_else(|| {
anyhow::anyhow!("can't render a panel button without an icon tooltip")
})
.log_err()?;
let name = entry.panel.persistent_name();
let panel = entry.panel.clone();

View File

@@ -3040,6 +3040,17 @@ impl Pane {
move |_window, cx| Tooltip::for_action_in("Go Back", &GoBack, &focus_handle, cx)
});
let open_aside = IconButton::new("open_aside", IconName::Thread)
.icon_size(IconSize::Small)
.on_click({
let workspace = self.workspace.clone();
move |_, window, cx| {
workspace
.update(cx, |workspace, cx| workspace.toggle_panelet(window, cx))
.ok();
}
});
let navigate_forward = IconButton::new("navigate_forward", IconName::ArrowRight)
.icon_size(IconSize::Small)
.on_click({
@@ -3080,13 +3091,23 @@ impl Pane {
}
let unpinned_tabs = tab_items.split_off(self.pinned_tab_count);
let pinned_tabs = tab_items;
let render_aside_toggle = self
.workspace
.upgrade()
.map(|entity| !entity.read(cx).panelet)
.unwrap_or(false);
TabBar::new("tab_bar")
.when(render_aside_toggle, |tab_bar| {
tab_bar.start_child(open_aside)
})
.when(
self.display_nav_history_buttons.unwrap_or_default(),
|tab_bar| {
tab_bar
.start_child(navigate_backward)
.start_child(navigate_forward)
.pre_end_child(navigate_backward)
.pre_end_child(navigate_forward)
},
)
.map(|tab_bar| {

View File

@@ -0,0 +1,63 @@
use gpui::WeakEntity;
use ui::{
ActiveTheme as _, Clickable, Context, DynamicSpacing, IconButton, IconName, IconSize,
InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Styled as _, Tab, Window,
div, px,
};
use crate::Workspace;
impl Workspace {
pub fn toggle_panelet(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.panelet = !self.panelet;
// self.
}
}
#[derive(IntoElement)]
pub struct Panelet {
workspace: WeakEntity<Workspace>,
}
impl Panelet {
pub fn new(cx: &mut Context<Workspace>) -> Self {
let workspace = cx.weak_entity();
Self { workspace }
}
}
impl RenderOnce for Panelet {
fn render(self, _window: &mut Window, cx: &mut ui::App) -> impl IntoElement {
div()
.h_full()
.bg(cx.theme().colors().tab_bar_background)
.w(px(400.0))
.border_color(cx.theme().colors().border)
.border_r_1()
.child(
div()
.pt_1()
.id("panelet")
.flex()
.flex_none()
.w_full()
.h(Tab::container_height(cx))
.child(
div().px(DynamicSpacing::Base06.rems(cx)).child(
IconButton::new("open_panelet", IconName::Thread)
.icon_size(IconSize::Small)
.on_click(move |_, window, cx| {
self.workspace
.update(cx, |workspace, cx| {
workspace.toggle_panelet(window, cx)
})
.ok();
}),
),
),
)
// .child(
// // todo!(put content here)
// )
}
}

View File

@@ -6,6 +6,7 @@ mod modal_layer;
pub mod notifications;
pub mod pane;
pub mod pane_group;
mod panelet;
mod path_list;
mod persistence;
pub mod searchable;
@@ -126,11 +127,14 @@ pub use workspace_settings::{
};
use zed_actions::{Spawn, feedback::FileBugReport};
use crate::persistence::{
SerializedAxis,
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
};
use crate::{item::ItemBufferKind, notifications::NotificationId};
use crate::{
panelet::Panelet,
persistence::{
SerializedAxis,
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
},
};
pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
@@ -1180,6 +1184,7 @@ pub struct Workspace {
scheduled_tasks: Vec<Task<()>>,
last_open_dock_positions: Vec<DockPosition>,
removing: bool,
panelet: bool,
}
impl EventEmitter<Event> for Workspace {}
@@ -1524,6 +1529,7 @@ impl Workspace {
scheduled_tasks: Vec::new(),
last_open_dock_positions: Vec::new(),
removing: false,
panelet: false,
}
}
@@ -6698,6 +6704,11 @@ impl Render for Workspace {
window,
cx,
))
.when(self.panelet, |this| {
this.child(
Panelet::new(cx)
)
})
.child(
div()
.flex()

View File

@@ -9,6 +9,7 @@ mod quick_action_bar;
#[cfg(target_os = "windows")]
pub(crate) mod windows_only_instance;
use agent_ui::agents_panel::AgentsPanel;
use agent_ui::{AgentDiffToolbar, AgentPanelDelegate};
use anyhow::Context as _;
pub use app_menus::*;
@@ -590,6 +591,7 @@ fn initialize_panels(
cx.clone(),
);
let debug_panel = DebugPanel::load(workspace_handle.clone(), cx);
let agents_panel = cx.new(|cx| AgentsPanel::new(cx)).unwrap();
let (
project_panel,
@@ -611,9 +613,10 @@ fn initialize_panels(
workspace_handle.update_in(cx, |workspace, window, cx| {
workspace.add_panel(project_panel, window, cx);
workspace.add_panel(agents_panel, window, cx);
workspace.add_panel(git_panel, window, cx);
workspace.add_panel(outline_panel, window, cx);
workspace.add_panel(terminal_panel, window, cx);
workspace.add_panel(git_panel, window, cx);
workspace.add_panel(channels_panel, window, cx);
workspace.add_panel(notification_panel, window, cx);
workspace.add_panel(debug_panel, window, cx);