Show agent icon instead of folder icon in sidebar thread list (#49421)

Replace the generic folder icon next to thread entries in the workspace
sidebar with the actual agent icon (Claude, Zed Agent, Gemini, etc.) for
the active thread.

Release Notes:
- N/A

Co-authored-by: cameron <cameron.studdstreet@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
authored and GitHub committed 2026-02-17 15:36:22 -08:00
1 parent b88ecd61b2
commit ed5605bd2c
2 files changed
+68 -40

No files matched your search

+39 -27
View File
@@ -14,7 +14,6 @@ use settings::{LanguageModelProviderSetting, LanguageModelSelection};
use zed_actions::agent::{OpenClaudeAgentOnboardingModal, ReauthenticateAgent};
use crate::ManageProfiles;
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, CopyThreadToClipboard, Follow, InlineAssistant,
@@ -36,6 +35,7 @@ use crate::{
ExternalAgent, ExternalAgentInitialContent, NewExternalAgentThread,
NewNativeAgentThreadFromSummary,
};
use crate::{ManageProfiles, acp::thread_view::AcpThreadView};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
@@ -289,7 +289,7 @@ enum HistoryKind {
enum ActiveView {
Uninitialized,
AgentThread {
thread_view: Entity<AcpServerView>,
server_view: Entity<AcpServerView>,
},
TextThread {
text_thread_editor: Entity<TextThreadEditor>,
@@ -855,7 +855,7 @@ impl AgentPanel {
pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpServerView>> {
match &self.active_view {
ActiveView::AgentThread { thread_view, .. } => Some(thread_view),
ActiveView::AgentThread { server_view, .. } => Some(server_view),
ActiveView::Uninitialized
| ActiveView::TextThread { .. }
| ActiveView::History { .. }
@@ -1529,9 +1529,21 @@ impl AgentPanel {
}
}
pub fn as_active_server_view(&self) -> Option<&Entity<AcpServerView>> {
match &self.active_view {
ActiveView::AgentThread { server_view } => Some(server_view),
_ => None,
}
}
pub fn as_active_thread_view(&self, cx: &App) -> Option<Entity<AcpThreadView>> {
let server_view = self.as_active_server_view()?;
server_view.read(cx).active_thread().cloned()
}
pub fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
match &self.active_view {
ActiveView::AgentThread { thread_view, .. } => thread_view
ActiveView::AgentThread { server_view, .. } => server_view
.read(cx)
.active_thread()
.map(|r| r.read(cx).thread.clone()),
@@ -1541,8 +1553,8 @@ impl AgentPanel {
pub(crate) fn active_native_agent_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> {
match &self.active_view {
ActiveView::AgentThread { thread_view, .. } => {
thread_view.read(cx).as_native_thread(cx)
ActiveView::AgentThread { server_view, .. } => {
server_view.read(cx).as_native_thread(cx)
}
_ => None,
}
@@ -1592,8 +1604,8 @@ impl AgentPanel {
}
self._active_view_observation = match &self.active_view {
ActiveView::AgentThread { thread_view } => {
Some(cx.observe(thread_view, |this, _, cx| {
ActiveView::AgentThread { server_view } => {
Some(cx.observe(server_view, |this, _, cx| {
cx.emit(AgentPanelEvent::ActiveViewChanged);
this.serialize(cx);
cx.notify();
@@ -1844,7 +1856,7 @@ impl AgentPanel {
.is_some()
.then(|| self.thread_store.clone());
let thread_view = cx.new(|cx| {
let server_view = cx.new(|cx| {
crate::acp::AcpServerView::new(
server,
resume_thread,
@@ -1859,7 +1871,7 @@ impl AgentPanel {
)
});
self.set_active_view(ActiveView::AgentThread { thread_view }, true, window, cx);
self.set_active_view(ActiveView::AgentThread { server_view }, true, window, cx);
}
}
@@ -1867,7 +1879,7 @@ impl Focusable for AgentPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self.active_view {
ActiveView::Uninitialized => self.focus_handle.clone(),
ActiveView::AgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::AgentThread { server_view, .. } => server_view.focus_handle(cx),
ActiveView::History { kind } => match kind {
HistoryKind::AgentThreads => self.acp_history.focus_handle(cx),
HistoryKind::TextThreads => self.text_thread_history.focus_handle(cx),
@@ -1988,13 +2000,13 @@ impl AgentPanel {
const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
let content = match &self.active_view {
ActiveView::AgentThread { thread_view } => {
let is_generating_title = thread_view
ActiveView::AgentThread { server_view } => {
let is_generating_title = server_view
.read(cx)
.as_native_thread(cx)
.map_or(false, |t| t.read(cx).is_generating_title());
if let Some(title_editor) = thread_view
if let Some(title_editor) = server_view
.read(cx)
.parent_thread(cx)
.map(|r| r.read(cx).title_editor.clone())
@@ -2002,7 +2014,7 @@ impl AgentPanel {
let container = div()
.w_full()
.on_action({
let thread_view = thread_view.downgrade();
let thread_view = server_view.downgrade();
move |_: &menu::Confirm, window, cx| {
if let Some(thread_view) = thread_view.upgrade() {
thread_view.focus_handle(cx).focus(window, cx);
@@ -2010,7 +2022,7 @@ impl AgentPanel {
}
})
.on_action({
let thread_view = thread_view.downgrade();
let thread_view = server_view.downgrade();
move |_: &editor::actions::Cancel, window, cx| {
if let Some(thread_view) = thread_view.upgrade() {
thread_view.focus_handle(cx).focus(window, cx);
@@ -2033,7 +2045,7 @@ impl AgentPanel {
container.into_any_element()
}
} else {
Label::new(thread_view.read(cx).title(cx))
Label::new(server_view.read(cx).title(cx))
.color(Color::Muted)
.truncate()
.into_any_element()
@@ -2171,12 +2183,12 @@ impl AgentPanel {
};
let thread_view = match &self.active_view {
ActiveView::AgentThread { thread_view } => Some(thread_view.clone()),
ActiveView::AgentThread { server_view } => Some(server_view.clone()),
_ => None,
};
let thread_with_messages = match &self.active_view {
ActiveView::AgentThread { thread_view } => {
thread_view.read(cx).has_user_submitted_prompt(cx)
ActiveView::AgentThread { server_view } => {
server_view.read(cx).has_user_submitted_prompt(cx)
}
_ => false,
};
@@ -2339,7 +2351,7 @@ impl AgentPanel {
};
let active_thread = match &self.active_view {
ActiveView::AgentThread { thread_view } => thread_view.read(cx).as_native_thread(cx),
ActiveView::AgentThread { server_view } => server_view.read(cx).as_native_thread(cx),
ActiveView::Uninitialized
| ActiveView::TextThread { .. }
| ActiveView::History { .. }
@@ -2773,8 +2785,8 @@ impl AgentPanel {
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
false
}
ActiveView::AgentThread { thread_view, .. }
if thread_view.read(cx).as_native_thread(cx).is_none() =>
ActiveView::AgentThread { server_view, .. }
if server_view.read(cx).as_native_thread(cx).is_none() =>
{
false
}
@@ -3046,8 +3058,8 @@ impl AgentPanel {
cx: &mut Context<Self>,
) {
match &self.active_view {
ActiveView::AgentThread { thread_view } => {
thread_view.update(cx, |thread_view, cx| {
ActiveView::AgentThread { server_view } => {
server_view.update(cx, |thread_view, cx| {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
@@ -3165,8 +3177,8 @@ impl Render for AgentPanel {
match &self.active_view {
ActiveView::Uninitialized => parent,
ActiveView::AgentThread { thread_view, .. } => parent
.child(thread_view.clone())
ActiveView::AgentThread { server_view, .. } => parent
.child(server_view.clone())
.child(self.render_drag_target(cx)),
ActiveView::History { kind } => match kind {
HistoryKind::AgentThreads => parent.child(self.acp_history.clone()),
+29 -13
View File
@@ -37,6 +37,7 @@ pub enum AgentThreadStatus {
struct AgentThreadInfo {
title: SharedString,
status: AgentThreadStatus,
icon: IconName,
}
const LAST_THREAD_TITLES_KEY: &str = "sidebar-last-thread-titles";
@@ -99,6 +100,7 @@ impl WorkspaceThreadEntry {
Some(AgentThreadInfo {
title: SharedString::from(title.clone()),
status: AgentThreadStatus::Completed,
icon: IconName::ZedAgent,
})
});
@@ -112,14 +114,23 @@ impl WorkspaceThreadEntry {
fn thread_info(workspace: &Entity<Workspace>, cx: &App) -> Option<AgentThreadInfo> {
let agent_panel = workspace.read(cx).panel::<AgentPanel>(cx)?;
let thread = agent_panel.read(cx).active_agent_thread(cx)?;
let thread_ref = thread.read(cx);
let title = thread_ref.title();
let status = match thread_ref.status() {
let agent_panel_ref = agent_panel.read(cx);
let thread_view = agent_panel_ref.as_active_thread_view(cx)?.read(cx);
let thread = thread_view.thread.read(cx);
let icon = thread_view.agent_icon;
let title = thread.title();
let status = match thread.status() {
ThreadStatus::Generating => AgentThreadStatus::Running,
ThreadStatus::Idle => AgentThreadStatus::Completed,
};
Some(AgentThreadInfo { title, status })
Some(AgentThreadInfo {
title,
status,
icon,
})
}
}
@@ -594,7 +605,6 @@ impl PickerDelegate for WorkspacePickerDelegate {
let workspace_index = thread_entry.index;
let multi_workspace = self.multi_workspace.clone();
let workspace_count = self.multi_workspace.read(cx).workspaces().len();
let is_active = self.active_workspace_index == workspace_index;
let is_hovered = self.hovered_thread_item == Some(workspace_index);
let remove_btn = IconButton::new(
@@ -628,11 +638,11 @@ impl PickerDelegate for WorkspacePickerDelegate {
("workspace-item", thread_entry.index),
thread_subtitle.unwrap_or("New Thread".into()),
)
.icon(if is_active {
IconName::FolderOpen
} else {
IconName::Folder
})
.icon(
thread_info
.as_ref()
.map_or(IconName::ZedAgent, |info| info.icon),
)
.running(running)
.generation_done(has_notification)
.selected(selected)
@@ -859,8 +869,14 @@ impl Sidebar {
title: SharedString,
status: AgentThreadStatus,
) {
self.test_thread_infos
.insert(index, AgentThreadInfo { title, status });
self.test_thread_infos.insert(
index,
AgentThreadInfo {
title,
status,
icon: IconName::ZedAgent,
},
);
}
#[cfg(any(test, feature = "test-support"))]