1use gpui::{HighlightStyle, SharedString};
7use std::ops::Range;
8use std::time::Duration;
9
10pub struct SyntaxHighlighter;
12
13impl SyntaxHighlighter {
14 pub fn new(_language: impl AsRef<str>) -> Self {
15 Self
16 }
17
18 pub fn highlight(&self, _text: &ropey::Rope) -> Vec<(Range<usize>, HighlightStyle)> {
19 Vec::new()
20 }
21
22 pub fn styles(
23 &self,
24 range: &Range<usize>,
25 _theme: &HighlightTheme,
26 ) -> Vec<(Range<usize>, HighlightStyle)> {
27 vec![(range.clone(), HighlightStyle::default())]
29 }
30
31 pub fn update(
32 &mut self,
33 _edit: Option<crate::input::InputEdit>,
34 _text: &ropey::Rope,
35 _timeout: Option<Duration>,
36 ) -> bool {
37 true
39 }
40
41 pub fn edit_tree(&mut self, _edit: Option<crate::input::InputEdit>, _text: &ropey::Rope) {
42 }
44
45 pub fn language(&self) -> &SharedString {
46 static EMPTY: SharedString = SharedString::new_static("");
47 &EMPTY
48 }
49
50 pub fn text(&self) -> &ropey::Rope {
51 static EMPTY_ROPE: LazyLock<ropey::Rope> = LazyLock::new(ropey::Rope::new);
52 &EMPTY_ROPE
53 }
54
55 pub fn tree(&self) -> Option<&crate::input::Tree> {
56 None
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum Language {
63 Unknown,
64}
65
66impl Language {
67 pub fn from_str(_name: &str) -> Self {
68 Language::Unknown
69 }
70
71 pub fn name(&self) -> &'static str {
72 "unknown"
73 }
74
75 pub fn config(&self) -> LanguageConfig {
76 LanguageConfig {
77 name: "unknown".into(),
78 }
79 }
80
81 pub fn all() -> impl Iterator<Item = Self> {
82 std::iter::once(Language::Unknown)
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct LanguageConfig {
89 pub name: SharedString,
90}
91
92impl LanguageConfig {
93 pub fn has_grammar(&self) -> bool {
94 false
95 }
96}
97
98use schemars::JsonSchema;
101use serde::{Deserialize, Serialize};
102use serde_repr::{Deserialize_repr, Serialize_repr};
103use std::{
104 collections::HashMap,
105 sync::{LazyLock, Mutex},
106};
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
109#[serde(rename_all = "lowercase")]
110pub enum FontStyle {
111 Normal,
112 Italic,
113 Underline,
114}
115
116#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, JsonSchema, Serialize_repr, Deserialize_repr)]
117#[repr(u16)]
118pub enum FontWeightContent {
119 Thin = 100,
120 ExtraLight = 200,
121 Light = 300,
122 Normal = 400,
123 Medium = 500,
124 Semibold = 600,
125 Bold = 700,
126 ExtraBold = 800,
127 Black = 900,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
131pub struct ThemeStyle {
132 pub color: Option<gpui::Hsla>,
133 pub font_style: Option<FontStyle>,
134 pub font_weight: Option<FontWeightContent>,
135}
136
137impl From<ThemeStyle> for HighlightStyle {
138 fn from(style: ThemeStyle) -> Self {
139 HighlightStyle {
140 color: style.color,
141 font_weight: style.font_weight.map(|w| match w {
142 FontWeightContent::Thin => gpui::FontWeight::THIN,
143 FontWeightContent::ExtraLight => gpui::FontWeight::EXTRA_LIGHT,
144 FontWeightContent::Light => gpui::FontWeight::LIGHT,
145 FontWeightContent::Normal => gpui::FontWeight::NORMAL,
146 FontWeightContent::Medium => gpui::FontWeight::MEDIUM,
147 FontWeightContent::Semibold => gpui::FontWeight::SEMIBOLD,
148 FontWeightContent::Bold => gpui::FontWeight::BOLD,
149 FontWeightContent::ExtraBold => gpui::FontWeight::EXTRA_BOLD,
150 FontWeightContent::Black => gpui::FontWeight::BLACK,
151 }),
152 font_style: style.font_style.map(|s| match s {
153 FontStyle::Normal => gpui::FontStyle::Normal,
154 FontStyle::Italic => gpui::FontStyle::Italic,
155 FontStyle::Underline => gpui::FontStyle::Normal,
156 }),
157 ..Default::default()
158 }
159 }
160}
161
162#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
163pub struct SyntaxColors {
164 pub attribute: Option<ThemeStyle>,
165 pub boolean: Option<ThemeStyle>,
166 pub comment: Option<ThemeStyle>,
167 pub comment_doc: Option<ThemeStyle>,
168 pub constant: Option<ThemeStyle>,
169 pub constructor: Option<ThemeStyle>,
170 pub embedded: Option<ThemeStyle>,
171 pub emphasis: Option<ThemeStyle>,
172 #[serde(rename = "emphasis.strong")]
173 pub emphasis_strong: Option<ThemeStyle>,
174 #[serde(rename = "enum")]
175 pub enum_: Option<ThemeStyle>,
176 pub function: Option<ThemeStyle>,
177 pub hint: Option<ThemeStyle>,
178 pub keyword: Option<ThemeStyle>,
179 pub label: Option<ThemeStyle>,
180 #[serde(rename = "link_text")]
181 pub link_text: Option<ThemeStyle>,
182 #[serde(rename = "link_uri")]
183 pub link_uri: Option<ThemeStyle>,
184 pub number: Option<ThemeStyle>,
185 pub operator: Option<ThemeStyle>,
186 pub predictive: Option<ThemeStyle>,
187 pub preproc: Option<ThemeStyle>,
188 pub primary: Option<ThemeStyle>,
189 pub property: Option<ThemeStyle>,
190 pub punctuation: Option<ThemeStyle>,
191 #[serde(rename = "punctuation.bracket")]
192 pub punctuation_bracket: Option<ThemeStyle>,
193 #[serde(rename = "punctuation.delimiter")]
194 pub punctuation_delimiter: Option<ThemeStyle>,
195 #[serde(rename = "punctuation.list_marker")]
196 pub punctuation_list_marker: Option<ThemeStyle>,
197 #[serde(rename = "punctuation.special")]
198 pub punctuation_special: Option<ThemeStyle>,
199 pub string: Option<ThemeStyle>,
200 #[serde(rename = "string.escape")]
201 pub string_escape: Option<ThemeStyle>,
202 #[serde(rename = "string.regex")]
203 pub string_regex: Option<ThemeStyle>,
204 #[serde(rename = "string.special")]
205 pub string_special: Option<ThemeStyle>,
206 #[serde(rename = "string.special.symbol")]
207 pub string_special_symbol: Option<ThemeStyle>,
208 pub tag: Option<ThemeStyle>,
209 #[serde(rename = "tag.doctype")]
210 pub tag_doctype: Option<ThemeStyle>,
211 #[serde(rename = "text.code.span")]
212 pub text_code_span: Option<ThemeStyle>,
213 #[serde(rename = "text.literal")]
214 pub text_literal: Option<ThemeStyle>,
215 pub title: Option<ThemeStyle>,
216 #[serde(rename = "type")]
217 pub type_: Option<ThemeStyle>,
218 pub variable: Option<ThemeStyle>,
219 #[serde(rename = "variable.special")]
220 pub variable_special: Option<ThemeStyle>,
221 pub variant: Option<ThemeStyle>,
222}
223
224impl SyntaxColors {
225 pub fn style(&self, name: &str) -> Option<HighlightStyle> {
226 if name.is_empty() {
227 return None;
228 }
229
230 let style = match name {
231 "attribute" => self.attribute,
232 "boolean" => self.boolean,
233 "comment" => self.comment,
234 "comment.doc" => self.comment_doc,
235 "constant" => self.constant,
236 "constructor" => self.constructor,
237 "embedded" => self.embedded,
238 "emphasis" => self.emphasis,
239 "emphasis.strong" => self.emphasis_strong,
240 "enum" => self.enum_,
241 "function" => self.function,
242 "hint" => self.hint,
243 "keyword" => self.keyword,
244 "label" => self.label,
245 "link_text" => self.link_text,
246 "link_uri" => self.link_uri,
247 "number" => self.number,
248 "operator" => self.operator,
249 "predictive" => self.predictive,
250 "preproc" => self.preproc,
251 "primary" => self.primary,
252 "property" => self.property,
253 "punctuation" => self.punctuation,
254 "punctuation.bracket" => self.punctuation_bracket,
255 "punctuation.delimiter" => self.punctuation_delimiter,
256 "punctuation.list_marker" => self.punctuation_list_marker,
257 "punctuation.special" => self.punctuation_special,
258 "string" => self.string,
259 "string.escape" => self.string_escape,
260 "string.regex" => self.string_regex,
261 "string.special" => self.string_special,
262 "string.special.symbol" => self.string_special_symbol,
263 "tag" => self.tag,
264 "tag.doctype" => self.tag_doctype,
265 "text.code.span" => self.text_code_span,
266 "text.literal" => self.text_literal,
267 "title" => self.title,
268 "type" => self.type_,
269 "variable" => self.variable,
270 "variable.special" => self.variable_special,
271 "variant" => self.variant,
272 _ => None,
273 }
274 .map(|s| s.into());
275
276 if style.is_some() {
277 style
278 } else if name.contains('.') {
279 name.split('.').next().and_then(|prefix| self.style(prefix))
280 } else {
281 None
282 }
283 }
284
285 pub fn style_for_index(&self, index: usize) -> Option<HighlightStyle> {
286 const HIGHLIGHT_NAMES: [&str; 41] = [
287 "attribute",
288 "boolean",
289 "comment",
290 "comment.doc",
291 "constant",
292 "constructor",
293 "embedded",
294 "emphasis",
295 "emphasis.strong",
296 "enum",
297 "function",
298 "hint",
299 "keyword",
300 "label",
301 "link_text",
302 "link_uri",
303 "number",
304 "operator",
305 "predictive",
306 "preproc",
307 "primary",
308 "property",
309 "punctuation",
310 "punctuation.bracket",
311 "punctuation.delimiter",
312 "punctuation.list_marker",
313 "punctuation.special",
314 "string",
315 "string.escape",
316 "string.regex",
317 "string.special",
318 "string.special.symbol",
319 "tag",
320 "tag.doctype",
321 "text.code.span",
322 "text.literal",
323 "title",
324 "type",
325 "variable",
326 "variable.special",
327 "variant",
328 ];
329
330 HIGHLIGHT_NAMES.get(index).and_then(|name| self.style(name))
331 }
332}
333
334#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
335pub struct StatusColors {
336 }
338
339impl StatusColors {
340 pub fn error(&self, _cx: &gpui::App) -> gpui::Hsla {
341 gpui::Hsla::default()
342 }
343
344 pub fn error_background(&self, _cx: &gpui::App) -> gpui::Hsla {
345 gpui::Hsla::default()
346 }
347
348 pub fn error_border(&self, _cx: &gpui::App) -> gpui::Hsla {
349 gpui::Hsla::default()
350 }
351
352 pub fn warning(&self, _cx: &gpui::App) -> gpui::Hsla {
353 gpui::Hsla::default()
354 }
355
356 pub fn warning_background(&self, _cx: &gpui::App) -> gpui::Hsla {
357 gpui::Hsla::default()
358 }
359
360 pub fn warning_border(&self, _cx: &gpui::App) -> gpui::Hsla {
361 gpui::Hsla::default()
362 }
363
364 pub fn info(&self, _cx: &gpui::App) -> gpui::Hsla {
365 gpui::Hsla::default()
366 }
367
368 pub fn info_background(&self, _cx: &gpui::App) -> gpui::Hsla {
369 gpui::Hsla::default()
370 }
371
372 pub fn info_border(&self, _cx: &gpui::App) -> gpui::Hsla {
373 gpui::Hsla::default()
374 }
375
376 pub fn success(&self, _cx: &gpui::App) -> gpui::Hsla {
377 gpui::Hsla::default()
378 }
379
380 pub fn success_background(&self, _cx: &gpui::App) -> gpui::Hsla {
381 gpui::Hsla::default()
382 }
383
384 pub fn success_border(&self, _cx: &gpui::App) -> gpui::Hsla {
385 gpui::Hsla::default()
386 }
387
388 pub fn hint(&self, _cx: &gpui::App) -> gpui::Hsla {
389 gpui::Hsla::default()
390 }
391
392 pub fn hint_background(&self, _cx: &gpui::App) -> gpui::Hsla {
393 gpui::Hsla::default()
394 }
395
396 pub fn hint_border(&self, _cx: &gpui::App) -> gpui::Hsla {
397 gpui::Hsla::default()
398 }
399}
400
401#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
402pub struct HighlightThemeStyle {
403 pub editor_background: Option<gpui::Hsla>,
404 pub editor_foreground: Option<gpui::Hsla>,
405 pub editor_active_line: Option<gpui::Hsla>,
406 pub editor_line_number: Option<gpui::Hsla>,
407 pub editor_active_line_number: Option<gpui::Hsla>,
408 pub editor_invisible: Option<gpui::Hsla>,
409 #[serde(rename = "editor.gutter.background")]
410 pub editor_gutter_background: Option<gpui::Hsla>,
411 #[serde(flatten)]
412 pub status: StatusColors,
413 #[serde(rename = "syntax")]
414 pub syntax: SyntaxColors,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
418pub struct HighlightTheme {
419 pub name: String,
420 #[serde(default)]
421 pub appearance: crate::ThemeMode,
422 pub style: HighlightThemeStyle,
423}
424
425impl std::ops::Deref for HighlightTheme {
426 type Target = SyntaxColors;
427
428 fn deref(&self) -> &Self::Target {
429 &self.style.syntax
430 }
431}
432
433impl HighlightTheme {
434 pub fn default_dark() -> std::sync::Arc<Self> {
435 use crate::DEFAULT_THEME_COLORS;
436 DEFAULT_THEME_COLORS[&crate::ThemeMode::Dark].1.clone()
437 }
438
439 pub fn default_light() -> std::sync::Arc<Self> {
440 use crate::DEFAULT_THEME_COLORS;
441 DEFAULT_THEME_COLORS[&crate::ThemeMode::Light].1.clone()
442 }
443}
444
445impl gpui_base::input::HighlightStyleResolver for HighlightTheme {
446 fn style(&self, name: &str) -> Option<HighlightStyle> {
447 self.style.syntax.style(name)
448 }
449}
450
451pub struct LanguageRegistry {
453 languages: Mutex<HashMap<SharedString, LanguageConfig>>,
454}
455
456impl LanguageRegistry {
457 pub fn singleton() -> &'static LazyLock<LanguageRegistry> {
458 static INSTANCE: LazyLock<LanguageRegistry> = LazyLock::new(|| LanguageRegistry {
459 languages: Mutex::new(HashMap::new()),
460 });
461 &INSTANCE
462 }
463
464 pub fn register(&self, lang: &str, config: &LanguageConfig) {
465 self.languages
466 .lock()
467 .unwrap()
468 .insert(lang.to_string().into(), config.clone());
469 }
470
471 pub fn languages(&self) -> Vec<SharedString> {
472 self.languages.lock().unwrap().keys().cloned().collect()
473 }
474
475 pub fn language(&self, name: &str) -> Option<LanguageConfig> {
476 self.languages.lock().unwrap().get(name).cloned()
477 }
478}