1use gpui::{App, FontWeight, Global, Hsla, SharedString};
28use theme::Theme;
29
30#[derive(Clone, Debug, Default, PartialEq, Eq)]
32pub struct Marks {
33 entries: Vec<Entry>,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub(crate) struct Entry {
38 pub(crate) name: SharedString,
39 pub(crate) delimiter: SharedString,
40}
41
42impl Marks {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn with(
53 mut self,
54 name: impl Into<SharedString>,
55 delimiter: impl Into<SharedString>,
56 ) -> Self {
57 let (name, delimiter) = (name.into(), delimiter.into());
58 let taken = self
59 .entries
60 .iter()
61 .any(|entry| entry.name == name || entry.delimiter == delimiter);
62 if !delimiter.is_empty() && !taken {
63 self.entries.push(Entry { name, delimiter });
64 }
65 self
66 }
67
68 pub fn is_empty(&self) -> bool {
69 self.entries.is_empty()
70 }
71
72 pub fn names(&self) -> impl Iterator<Item = &SharedString> {
74 self.entries.iter().map(|entry| &entry.name)
75 }
76
77 pub fn delimiter(&self, name: &str) -> Option<&str> {
79 self.entries
80 .iter()
81 .find(|entry| entry.name == name)
82 .map(|entry| entry.delimiter.as_ref())
83 }
84
85 pub(crate) fn sorted(&self) -> Vec<&Entry> {
88 let mut entries: Vec<&Entry> = self.entries.iter().collect();
89 entries.sort_by_key(|entry| std::cmp::Reverse(entry.delimiter.len()));
90 entries
91 }
92
93 pub(crate) fn index(&self, ix: usize) -> Option<&Entry> {
94 self.entries.get(ix)
95 }
96
97 pub(crate) fn position(&self, entry: &Entry) -> Option<usize> {
98 self.entries.iter().position(|row| row == entry)
99 }
100
101 pub(crate) fn of(cx: &App) -> Self {
103 cx.try_global::<Installed>()
104 .map_or_else(Self::default, |installed| installed.0.clone())
105 }
106}
107
108struct Installed(Marks);
109
110impl Global for Installed {}
111
112pub(crate) fn set_marks(cx: &mut App, marks: Marks) {
115 cx.set_global(Installed(marks));
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq)]
120pub struct MarkPaint {
121 pub color: Option<Hsla>,
122 pub background: Option<Hsla>,
124 pub weight: Option<FontWeight>,
125 pub italic: bool,
126 pub underline: bool,
127 pub strikethrough: bool,
128}
129
130pub type Painter = fn(name: &str, theme: &Theme) -> Option<MarkPaint>;
133
134struct InstalledPaint(Painter);
135
136impl Global for InstalledPaint {}
137
138pub(crate) fn set_mark_paint(cx: &mut App, paint: Painter) {
142 cx.set_global(InstalledPaint(paint));
143}
144
145pub(crate) fn paint_of(cx: &App, name: &str, theme: &Theme) -> Option<MarkPaint> {
146 (cx.try_global::<InstalledPaint>()?.0)(name, theme)
147}
148
149#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
154#[non_exhaustive]
155pub enum HighlightColor {
156 #[default]
157 Yellow,
158 Green,
159 Blue,
160 Pink,
161 Purple,
162}
163
164impl HighlightColor {
165 pub const ALL: [Self; 5] = [
167 Self::Yellow,
168 Self::Green,
169 Self::Blue,
170 Self::Pink,
171 Self::Purple,
172 ];
173
174 pub fn name(self) -> &'static str {
177 match self {
178 Self::Yellow => "yellow",
179 Self::Green => "green",
180 Self::Blue => "blue",
181 Self::Pink => "pink",
182 Self::Purple => "purple",
183 }
184 }
185
186 pub fn from_name(name: &str) -> Option<Self> {
187 Self::ALL.into_iter().find(|color| color.name() == name)
188 }
189}
190
191pub type HighlightPaint = fn(color: HighlightColor, theme: &Theme) -> Hsla;
193
194struct InstalledHighlight(HighlightPaint);
195
196impl Global for InstalledHighlight {}
197
198pub(crate) fn set_highlight_paint(cx: &mut App, paint: HighlightPaint) {
201 cx.set_global(InstalledHighlight(paint));
202}
203
204pub(crate) fn highlight_paint_of(cx: &App) -> HighlightPaint {
205 cx.try_global::<InstalledHighlight>()
206 .map_or(default_highlight, |installed| installed.0)
207}
208
209pub fn highlight_solid(color: HighlightColor, theme: &Theme) -> Hsla {
212 let dark = theme.appearance == theme::Appearance::Dark;
213 let hex = match color {
214 HighlightColor::Yellow if dark => 0xFFD60A,
215 HighlightColor::Yellow => 0xFFCC00,
216 HighlightColor::Green if dark => 0x32D74B,
217 HighlightColor::Green => 0x28CD41,
218 HighlightColor::Blue if dark => 0x0A84FF,
219 HighlightColor::Blue => 0x007AFF,
220 HighlightColor::Pink if dark => 0xFF375F,
221 HighlightColor::Pink => 0xFF2D55,
222 HighlightColor::Purple if dark => 0xBF5AF2,
223 HighlightColor::Purple => 0xAF52DE,
224 };
225 gpui::rgb(hex).into()
226}
227
228pub fn default_highlight(color: HighlightColor, theme: &Theme) -> Hsla {
231 let alpha = match theme.appearance {
232 theme::Appearance::Dark => 0.32,
233 theme::Appearance::Light => 0.45,
234 };
235 highlight_solid(color, theme).opacity(alpha)
236}