Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 2 additions & 41 deletions crates/sidebar/src/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,37 +226,6 @@ impl ThreadEntryWorkspace {
}
}

/// If the title begins with a non-letter, non-whitespace character (such as a
/// leading emoji or symbol the user prefixed their title with), splits that
/// character out so it can be displayed in place of the entry's icon
fn split_leading_icon_char(
title: &SharedString,
highlight_positions: &[usize],
) -> Option<(SharedString, SharedString, Vec<usize>)> {
let first_char = title.chars().next()?;
if first_char.is_alphabetic() || first_char.is_whitespace() {
return None;
}

let trimmed_title = title[first_char.len_utf8()..].trim_start();
if trimmed_title.is_empty() {
return None;
}

let stripped_len = title.len() - trimmed_title.len();
let adjusted_positions = highlight_positions
.iter()
.filter(|&&position| position >= stripped_len)
.map(|&position| position - stripped_len)
.collect();

Some((
first_char.to_string().into(),
trimmed_title.to_string().into(),
adjusted_positions,
))
}

fn draft_display_label_for_thread_metadata(
metadata: &ThreadMetadata,
workspace: &ThreadEntryWorkspace,
Expand Down Expand Up @@ -5926,22 +5895,14 @@ impl Sidebar {
);
let is_remote = terminal.workspace.is_remote(cx);

let display_title = terminal.metadata.display_title();
let (icon_char, title, highlight_positions) =
match split_leading_icon_char(&display_title, &terminal.highlight_positions) {
Some((icon_char, title, positions)) => (Some(icon_char), title, positions),
None => (None, display_title, terminal.highlight_positions.clone()),
};

ThreadItem::new(id, title)
ThreadItem::new(id, terminal.metadata.display_title())
.base_bg(sidebar_bg)
.icon(IconName::Terminal)
.when_some(icon_char, |this, icon_char| this.icon_char(icon_char))
.is_remote(is_remote)
.worktrees(worktrees)
.timestamp(timestamp)
.notified(terminal.has_notification)
.highlight_positions(highlight_positions)
.highlight_positions(terminal.highlight_positions.clone())
.selected(is_active)
.focused(is_focused)
.hovered(is_hovered)
Expand Down
34 changes: 0 additions & 34 deletions crates/sidebar/src/sidebar_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14730,37 +14730,3 @@ async fn test_cmd_click_project_header_returns_to_last_active_linked_worktree_wo
linked-worktree workspace was the last-active one for the group"
);
}

#[test]
fn test_split_leading_icon_char() {
// A leading symbol is pulled out and trimmed from the title.
let (icon, title, positions) =
split_leading_icon_char(&"✳ Implement separate config".into(), &[]).unwrap();
assert_eq!(icon.as_ref(), "✳");
assert_eq!(title.as_ref(), "Implement separate config");
assert_eq!(positions, Vec::<usize>::new());

// No leading symbol when the title starts with a letter.
assert!(split_leading_icon_char(&"Implement separate config".into(), &[]).is_none());

// Whitespace is not treated as an icon character.
assert!(split_leading_icon_char(&" leading space".into(), &[]).is_none());

// Numbers are non-letters, so they are treated as icon characters.
let (icon, title, _) = split_leading_icon_char(&"1 first".into(), &[]).unwrap();
assert_eq!(icon.as_ref(), "1");
assert_eq!(title.as_ref(), "first");

// A title consisting only of a symbol is left untouched.
assert!(split_leading_icon_char(&"✳".into(), &[]).is_none());

// Highlight positions are shifted to account for the stripped prefix, and
// positions that fall inside the stripped prefix are dropped.
let title: SharedString = "# abc".into();
let abc_offset = title.find('a').unwrap();
let (icon, trimmed, positions) =
split_leading_icon_char(&title, &[0, abc_offset, abc_offset + 1]).unwrap();
assert_eq!(icon.as_ref(), "#");
assert_eq!(trimmed.as_ref(), "abc");
assert_eq!(positions, vec![0, 1]);
}
22 changes: 2 additions & 20 deletions crates/ui/src/components/ai/thread_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ pub struct ThreadItemWorktreeInfo {
pub struct ThreadItem {
id: ElementId,
icon: IconName,
icon_char: Option<SharedString>,
icon_color: Option<Color>,
icon_visible: bool,
custom_icon_from_external_svg: Option<SharedString>,
Expand Down Expand Up @@ -70,7 +69,6 @@ impl ThreadItem {
Self {
id: id.into(),
icon: IconName::ZedAgent,
icon_char: None,
icon_color: None,
icon_visible: true,
custom_icon_from_external_svg: None,
Expand Down Expand Up @@ -111,13 +109,6 @@ impl ThreadItem {
self
}

/// Renders the given character in place of the icon. Takes precedence over
/// [`Self::icon`] and [`Self::custom_icon_from_external_svg`].
pub fn icon_char(mut self, icon_char: impl Into<SharedString>) -> Self {
self.icon_char = Some(icon_char.into());
self
}

pub fn icon_color(mut self, color: Color) -> Self {
self.icon_color = Some(color);
self
Expand Down Expand Up @@ -292,21 +283,12 @@ impl RenderOnce for ThreadItem {
.when(!icon_visible, |this| this.invisible())
};
let icon_color = self.icon_color.unwrap_or(Color::Muted);
let agent_icon = if let Some(icon_char) = self.icon_char {
Label::new(icon_char)
.size(LabelSize::Small)
.color(icon_color)
.into_any_element()
} else if let Some(custom_svg) = self.custom_icon_from_external_svg {
let agent_icon = if let Some(custom_svg) = self.custom_icon_from_external_svg {
Icon::from_external_svg(custom_svg)
.color(icon_color)
.size(IconSize::Small)
.into_any_element()
} else {
Icon::new(self.icon)
.color(icon_color)
.size(IconSize::Small)
.into_any_element()
Icon::new(self.icon).color(icon_color).size(IconSize::Small)
};

let status_icon = if self.status == AgentThreadStatus::Error {
Expand Down
Loading