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 update_batch(
49 &mut self,
50 edits: &[(InputEdit, Rope)],
51 folding: bool,
52 window: &mut Window,
53 cx: &mut Context<EditorState>,
54 ) {
55 for (edit, text) in edits {
56 self.update(Some(*edit), text, folding, window, cx);
57 }
58 }
59
60 fn styles(
63 &self,
64 range: &Range<usize>,
65 resolver: &dyn HighlightStyleResolver,
66 ) -> Vec<(Range<usize>, HighlightStyle)>;
67
68 fn fold_ranges(&self, text: &Rope) -> Vec<FoldRange>;
69
70 fn fold_ranges_for_edit(&self, range: Range<usize>, text: &Rope) -> Vec<FoldRange> {
71 let _ = range;
72 self.fold_ranges(text)
73 }
74}
75
76pub type InputHighlighterFactory = Rc<dyn Fn(&str) -> Option<Box<dyn InputHighlighter>>>;
77pub type SharedHighlightStyleResolver = Arc<dyn HighlightStyleResolver>;
78pub type FoldIconRenderer = Rc<dyn Fn(usize, bool) -> AnyElement>;
79pub type FoldPlaceholderRenderer = Rc<dyn Fn(usize) -> AnyElement>;
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum SyntaxContext {
87 Code,
88 String,
89 Comment,
90}
91
92pub trait SyntaxContextProvider {
97 fn context_at(&self, text: &Rope, offset: usize) -> SyntaxContext;
98}
99
100#[derive(Clone, Copy, Default)]
101pub struct DiagnosticColors {
102 pub error: Hsla,
103 pub warning: Hsla,
104 pub info: Hsla,
105 pub hint: Hsla,
106}
107
108#[derive(Clone)]
110pub struct InputEditorStyle {
111 pub foreground: Hsla,
112 pub muted_foreground: Hsla,
113 pub background: Hsla,
114 pub border: Hsla,
115 pub selection: Hsla,
116 pub caret: Hsla,
117 pub diagnostics: DiagnosticColors,
118 pub highlight_styles: SharedHighlightStyleResolver,
119 pub editor_invisible: Option<Hsla>,
120 pub editor_active_line: Option<Hsla>,
121 pub editor_gutter_background: Option<Hsla>,
122 pub fold_icon_renderer: Option<FoldIconRenderer>,
123}
124
125impl InputEditorStyle {
126 pub fn resolved(&self, tokens: &SemanticThemeTokens) -> Self {
139 let colors = &tokens.colors;
140 let unset = |value: Hsla| value.a == 0.;
141 let or = |value: Hsla, fallback: Hsla| if unset(value) { fallback } else { value };
142
143 let foreground = or(self.foreground, colors.foreground);
144 let mut selection = self.selection;
145 if unset(selection) {
146 selection = colors.accent;
147 selection.a = 0.4;
149 }
150
151 Self {
152 foreground,
153 muted_foreground: or(self.muted_foreground, colors.muted_foreground),
154 background: or(self.background, colors.surface),
155 border: or(self.border, colors.border),
156 selection,
157 caret: or(self.caret, foreground),
158 ..self.clone()
159 }
160 }
161}
162
163impl Default for InputEditorStyle {
164 fn default() -> Self {
165 Self {
166 foreground: Hsla::default(),
167 muted_foreground: Hsla::default(),
168 background: Hsla::default(),
169 border: Hsla::default(),
170 selection: Hsla::default(),
171 caret: Hsla::default(),
172 diagnostics: DiagnosticColors::default(),
173 highlight_styles: Arc::new(NoHighlightStyles),
174 editor_invisible: None,
175 editor_active_line: None,
176 editor_gutter_background: None,
177 fold_icon_renderer: None,
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use gpui::hsla;
185
186 use super::InputEditorStyle;
187 use crate::SemanticThemeTokens;
188
189 fn dark() -> SemanticThemeTokens {
190 let mut tokens = SemanticThemeTokens::default();
191 tokens.colors.foreground = hsla(0., 0., 0.98, 1.0);
192 tokens.colors.muted_foreground = hsla(0., 0., 0.64, 1.0);
193 tokens.colors.surface = hsla(0., 0., 0.04, 1.0);
194 tokens.colors.border = hsla(0., 0., 0.15, 1.0);
195 tokens.colors.accent = hsla(0.6, 0.5, 0.5, 1.0);
196 tokens
197 }
198
199 #[test]
200 fn an_unprojected_style_takes_its_ink_from_the_palette() {
201 let tokens = dark();
202 let resolved = InputEditorStyle::default().resolved(&tokens);
203
204 assert_eq!(resolved.foreground, tokens.colors.foreground);
205 assert_eq!(resolved.caret, tokens.colors.foreground);
206 assert_eq!(resolved.muted_foreground, tokens.colors.muted_foreground);
207 assert_eq!(resolved.background, tokens.colors.surface);
208 assert_eq!(resolved.border, tokens.colors.border);
209 for colour in [
212 resolved.foreground,
213 resolved.caret,
214 resolved.muted_foreground,
215 resolved.selection,
216 ] {
217 assert!(colour.a > 0., "{colour:?} is still invisible");
218 }
219 }
220
221 #[test]
222 fn a_selection_stays_translucent_enough_to_read_through() {
223 let resolved = InputEditorStyle::default().resolved(&dark());
224 assert_eq!(resolved.selection.a, 0.4);
225 }
226
227 #[test]
228 fn projected_colours_are_kept_verbatim() {
229 let chosen = hsla(0.3, 0.4, 0.5, 1.0);
230 let style = InputEditorStyle {
231 foreground: chosen,
232 caret: chosen,
233 ..Default::default()
234 };
235 let resolved = style.resolved(&dark());
236
237 assert_eq!(resolved.foreground, chosen);
238 assert_eq!(resolved.caret, chosen);
239 assert_eq!(resolved.border, dark().colors.border);
241 }
242
243 #[test]
244 fn resolution_never_consumes_its_own_output() {
245 let projected = InputEditorStyle::default();
249 let first = projected.resolved(&dark());
250
251 let mut light = SemanticThemeTokens::default();
252 light.colors.foreground = hsla(0., 0., 0.04, 1.0);
253 let second = projected.resolved(&light);
254
255 assert_ne!(first.foreground, second.foreground);
256 assert_eq!(second.foreground, light.colors.foreground);
257 }
258}