1use std::{ops::Range, rc::Rc, sync::Arc};
2
3use gpui::{AnyElement, Context, HighlightStyle, Hsla, SharedString, Window};
4use ropey::Rope;
5
6use super::{EditorState, FoldRange, InputEdit};
7use crate::SemanticThemeTokens;
8
9pub trait HighlightStyleResolver: Send + Sync {
14 fn style(&self, name: &str) -> Option<HighlightStyle>;
15}
16
17#[derive(Default)]
18struct NoHighlightStyles;
19
20impl HighlightStyleResolver for NoHighlightStyles {
21 fn style(&self, _: &str) -> Option<HighlightStyle> {
22 None
23 }
24}
25
26pub trait InputHighlighter {
31 fn language(&self) -> SharedString;
32
33 fn update(
34 &mut self,
35 edit: Option<InputEdit>,
36 text: &Rope,
37 folding: bool,
38 window: &mut Window,
39 cx: &mut Context<EditorState>,
40 );
41
42 fn styles(
45 &self,
46 range: &Range<usize>,
47 resolver: &dyn HighlightStyleResolver,
48 ) -> Vec<(Range<usize>, HighlightStyle)>;
49
50 fn fold_ranges(&self, text: &Rope) -> Vec<FoldRange>;
51
52 fn fold_ranges_for_edit(&self, range: Range<usize>, text: &Rope) -> Vec<FoldRange> {
53 let _ = range;
54 self.fold_ranges(text)
55 }
56}
57
58pub type InputHighlighterFactory = Rc<dyn Fn(&str) -> Option<Box<dyn InputHighlighter>>>;
59pub type SharedHighlightStyleResolver = Arc<dyn HighlightStyleResolver>;
60pub type FoldIconRenderer = Rc<dyn Fn(usize, bool) -> AnyElement>;
61
62#[derive(Clone, Copy, Default)]
63pub struct DiagnosticColors {
64 pub error: Hsla,
65 pub warning: Hsla,
66 pub info: Hsla,
67 pub hint: Hsla,
68}
69
70#[derive(Clone)]
72pub struct InputEditorStyle {
73 pub foreground: Hsla,
74 pub muted_foreground: Hsla,
75 pub background: Hsla,
76 pub border: Hsla,
77 pub selection: Hsla,
78 pub caret: Hsla,
79 pub diagnostics: DiagnosticColors,
80 pub highlight_styles: SharedHighlightStyleResolver,
81 pub editor_invisible: Option<Hsla>,
82 pub editor_active_line: Option<Hsla>,
83 pub editor_gutter_background: Option<Hsla>,
84 pub fold_icon_renderer: Option<FoldIconRenderer>,
85}
86
87impl InputEditorStyle {
88 pub fn resolved(&self, tokens: &SemanticThemeTokens) -> Self {
101 let colors = &tokens.colors;
102 let unset = |value: Hsla| value.a == 0.;
103 let or = |value: Hsla, fallback: Hsla| if unset(value) { fallback } else { value };
104
105 let foreground = or(self.foreground, colors.foreground);
106 let mut selection = self.selection;
107 if unset(selection) {
108 selection = colors.accent;
109 selection.a = 0.4;
111 }
112
113 Self {
114 foreground,
115 muted_foreground: or(self.muted_foreground, colors.muted_foreground),
116 background: or(self.background, colors.surface),
117 border: or(self.border, colors.border),
118 selection,
119 caret: or(self.caret, foreground),
120 ..self.clone()
121 }
122 }
123}
124
125impl Default for InputEditorStyle {
126 fn default() -> Self {
127 Self {
128 foreground: Hsla::default(),
129 muted_foreground: Hsla::default(),
130 background: Hsla::default(),
131 border: Hsla::default(),
132 selection: Hsla::default(),
133 caret: Hsla::default(),
134 diagnostics: DiagnosticColors::default(),
135 highlight_styles: Arc::new(NoHighlightStyles),
136 editor_invisible: None,
137 editor_active_line: None,
138 editor_gutter_background: None,
139 fold_icon_renderer: None,
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use gpui::hsla;
147
148 use super::InputEditorStyle;
149 use crate::SemanticThemeTokens;
150
151 fn dark() -> SemanticThemeTokens {
152 let mut tokens = SemanticThemeTokens::default();
153 tokens.colors.foreground = hsla(0., 0., 0.98, 1.0);
154 tokens.colors.muted_foreground = hsla(0., 0., 0.64, 1.0);
155 tokens.colors.surface = hsla(0., 0., 0.04, 1.0);
156 tokens.colors.border = hsla(0., 0., 0.15, 1.0);
157 tokens.colors.accent = hsla(0.6, 0.5, 0.5, 1.0);
158 tokens
159 }
160
161 #[test]
162 fn an_unprojected_style_takes_its_ink_from_the_palette() {
163 let tokens = dark();
164 let resolved = InputEditorStyle::default().resolved(&tokens);
165
166 assert_eq!(resolved.foreground, tokens.colors.foreground);
167 assert_eq!(resolved.caret, tokens.colors.foreground);
168 assert_eq!(resolved.muted_foreground, tokens.colors.muted_foreground);
169 assert_eq!(resolved.background, tokens.colors.surface);
170 assert_eq!(resolved.border, tokens.colors.border);
171 for colour in [
174 resolved.foreground,
175 resolved.caret,
176 resolved.muted_foreground,
177 resolved.selection,
178 ] {
179 assert!(colour.a > 0., "{colour:?} is still invisible");
180 }
181 }
182
183 #[test]
184 fn a_selection_stays_translucent_enough_to_read_through() {
185 let resolved = InputEditorStyle::default().resolved(&dark());
186 assert_eq!(resolved.selection.a, 0.4);
187 }
188
189 #[test]
190 fn projected_colours_are_kept_verbatim() {
191 let chosen = hsla(0.3, 0.4, 0.5, 1.0);
192 let style = InputEditorStyle {
193 foreground: chosen,
194 caret: chosen,
195 ..Default::default()
196 };
197 let resolved = style.resolved(&dark());
198
199 assert_eq!(resolved.foreground, chosen);
200 assert_eq!(resolved.caret, chosen);
201 assert_eq!(resolved.border, dark().colors.border);
203 }
204
205 #[test]
206 fn resolution_never_consumes_its_own_output() {
207 let projected = InputEditorStyle::default();
211 let first = projected.resolved(&dark());
212
213 let mut light = SemanticThemeTokens::default();
214 light.colors.foreground = hsla(0., 0., 0.04, 1.0);
215 let second = projected.resolved(&light);
216
217 assert_ne!(first.foreground, second.foreground);
218 assert_eq!(second.foreground, light.colors.foreground);
219 }
220}