1use std::rc::Rc;
2
3use gpui::ClickEvent;
4use ui::{prelude::*, IconButtonShape, Tooltip};
5
6use crate::context::{Context, ContextKind};
7
8#[derive(IntoElement)]
9pub enum ContextPill {
10 Added {
11 context: Context,
12 dupe_name: bool,
13 on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
14 },
15 Suggested {
16 name: SharedString,
17 kind: ContextKind,
18 on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
19 },
20}
21
22impl ContextPill {
23 pub fn new_added(
24 context: Context,
25 dupe_name: bool,
26 on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
27 ) -> Self {
28 Self::Added {
29 context,
30 dupe_name,
31 on_remove,
32 }
33 }
34
35 pub fn new_suggested(
36 name: SharedString,
37 kind: ContextKind,
38 on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
39 ) -> Self {
40 Self::Suggested { name, kind, on_add }
41 }
42
43 pub fn id(&self) -> ElementId {
44 match self {
45 Self::Added { context, .. } => {
46 ElementId::NamedInteger("context-pill".into(), context.id.0)
47 }
48 Self::Suggested { .. } => "suggested-context-pill".into(),
49 }
50 }
51
52 pub fn kind(&self) -> &ContextKind {
53 match self {
54 Self::Added { context, .. } => &context.kind,
55 Self::Suggested { kind, .. } => kind,
56 }
57 }
58}
59
60impl RenderOnce for ContextPill {
61 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
62 let icon = match &self.kind() {
63 ContextKind::File => IconName::File,
64 ContextKind::Directory => IconName::Folder,
65 ContextKind::FetchedUrl => IconName::Globe,
66 ContextKind::Thread => IconName::MessageCircle,
67 };
68
69 let color = cx.theme().colors();
70
71 let base_pill = h_flex()
72 .id(self.id())
73 .pl_1()
74 .pb(px(1.))
75 .border_1()
76 .rounded_md()
77 .gap_1()
78 .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
79
80 match &self {
81 ContextPill::Added {
82 context,
83 dupe_name,
84 on_remove,
85 } => base_pill
86 .bg(color.element_background)
87 .border_color(color.border.opacity(0.5))
88 .pr(if on_remove.is_some() { px(2.) } else { px(4.) })
89 .child(
90 h_flex()
91 .id("context-data")
92 .gap_1()
93 .child(Label::new(context.name.clone()).size(LabelSize::Small))
94 .when_some(context.parent.as_ref(), |element, parent_name| {
95 if *dupe_name {
96 element.child(
97 Label::new(parent_name.clone())
98 .size(LabelSize::XSmall)
99 .color(Color::Muted),
100 )
101 } else {
102 element
103 }
104 })
105 .when_some(context.tooltip.clone(), |element, tooltip| {
106 element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
107 }),
108 )
109 .when_some(on_remove.as_ref(), |element, on_remove| {
110 element.child(
111 IconButton::new(("remove", context.id.0), IconName::Close)
112 .shape(IconButtonShape::Square)
113 .icon_size(IconSize::XSmall)
114 .tooltip(|cx| Tooltip::text("Remove Context", cx))
115 .on_click({
116 let on_remove = on_remove.clone();
117 move |event, cx| on_remove(event, cx)
118 }),
119 )
120 }),
121 ContextPill::Suggested { name, kind, on_add } => base_pill
122 .cursor_pointer()
123 .pr_1()
124 .border_color(color.border_variant.opacity(0.5))
125 .hover(|style| style.bg(color.element_hover.opacity(0.5)))
126 .child(
127 Label::new(name.clone())
128 .size(LabelSize::Small)
129 .color(Color::Muted),
130 )
131 .child(
132 Label::new(match kind {
133 ContextKind::File => "Open File",
134 ContextKind::Thread | ContextKind::Directory | ContextKind::FetchedUrl => {
135 "Active"
136 }
137 })
138 .size(LabelSize::XSmall)
139 .color(Color::Muted),
140 )
141 .child(
142 Icon::new(IconName::Plus)
143 .size(IconSize::XSmall)
144 .into_any_element(),
145 )
146 .tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
147 .on_click({
148 let on_add = on_add.clone();
149 move |event, cx| on_add(event, cx)
150 }),
151 }
152 }
153}