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) -> GrammarConfig {
76 GrammarConfig {
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
92pub type GrammarConfig = LanguageConfig;
94
95impl LanguageConfig {
96 pub fn has_grammar(&self) -> bool {
97 false
98 }
99}
100
101use schemars::JsonSchema;
104use serde::{Deserialize, Serialize};
105use serde_repr::{Deserialize_repr, Serialize_repr};
106use std::{
107 collections::HashMap,
108 sync::{LazyLock, Mutex},
109};
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
112#[serde(rename_all = "lowercase")]
113pub enum FontStyle {
114 Normal,
115 Italic,
116 Underline,
117}
118
119#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, JsonSchema, Serialize_repr, Deserialize_repr)]
120#[repr(u16)]
121pub enum FontWeightContent {
122 Thin = 100,
123 ExtraLight = 200,
124 Light = 300,
125 Normal = 400,
126 Medium = 500,
127 Semibold = 600,
128 Bold = 700,
129 ExtraBold = 800,
130 Black = 900,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
134pub struct ThemeStyle {
135 pub color: Option<gpui::Hsla>,
136 pub font_style: Option<FontStyle>,
137 pub font_weight: Option<FontWeightContent>,
138}
139
140impl From<ThemeStyle> for HighlightStyle {
141 fn from(style: ThemeStyle) -> Self {
142 HighlightStyle {
143 color: style.color,
144 font_weight: style.font_weight.map(|w| match w {
145 FontWeightContent::Thin => gpui::FontWeight::THIN,
146 FontWeightContent::ExtraLight => gpui::FontWeight::EXTRA_LIGHT,
147 FontWeightContent::Light => gpui::FontWeight::LIGHT,
148 FontWeightContent::Normal => gpui::FontWeight::NORMAL,
149 FontWeightContent::Medium => gpui::FontWeight::MEDIUM,
150 FontWeightContent::Semibold => gpui::FontWeight::SEMIBOLD,
151 FontWeightContent::Bold => gpui::FontWeight::BOLD,
152 FontWeightContent::ExtraBold => gpui::FontWeight::EXTRA_BOLD,
153 FontWeightContent::Black => gpui::FontWeight::BLACK,
154 }),
155 font_style: style.font_style.map(|s| match s {
156 FontStyle::Normal => gpui::FontStyle::Normal,
157 FontStyle::Italic => gpui::FontStyle::Italic,
158 FontStyle::Underline => gpui::FontStyle::Normal,
159 }),
160 ..Default::default()
161 }
162 }
163}
164
165#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
166pub struct SyntaxColors {
167 pub attribute: Option<ThemeStyle>,
168 pub boolean: Option<ThemeStyle>,
169 pub comment: Option<ThemeStyle>,
170 pub comment_doc: Option<ThemeStyle>,
171 pub constant: Option<ThemeStyle>,
172 pub constructor: Option<ThemeStyle>,
173 pub embedded: Option<ThemeStyle>,
174 pub emphasis: Option<ThemeStyle>,
175 #[serde(rename = "emphasis.strong")]
176 pub emphasis_strong: Option<ThemeStyle>,
177 #[serde(rename = "enum")]
178 pub enum_: Option<ThemeStyle>,
179 pub function: Option<ThemeStyle>,
180 pub hint: Option<ThemeStyle>,
181 pub keyword: Option<ThemeStyle>,
182 pub label: Option<ThemeStyle>,
183 #[serde(rename = "link_text")]
184 pub link_text: Option<ThemeStyle>,
185 #[serde(rename = "link_uri")]
186 pub link_uri: Option<ThemeStyle>,
187 pub number: Option<ThemeStyle>,
188 pub operator: Option<ThemeStyle>,
189 pub predictive: Option<ThemeStyle>,
190 pub preproc: Option<ThemeStyle>,
191 pub primary: Option<ThemeStyle>,
192 pub property: Option<ThemeStyle>,
193 pub punctuation: Option<ThemeStyle>,
194 #[serde(rename = "punctuation.bracket")]
195 pub punctuation_bracket: Option<ThemeStyle>,
196 #[serde(rename = "punctuation.delimiter")]
197 pub punctuation_delimiter: Option<ThemeStyle>,
198 #[serde(rename = "punctuation.list_marker")]
199 pub punctuation_list_marker: Option<ThemeStyle>,
200 #[serde(rename = "punctuation.special")]
201 pub punctuation_special: Option<ThemeStyle>,
202 pub string: Option<ThemeStyle>,
203 #[serde(rename = "string.escape")]
204 pub string_escape: Option<ThemeStyle>,
205 #[serde(rename = "string.regex")]
206 pub string_regex: Option<ThemeStyle>,
207 #[serde(rename = "string.special")]
208 pub string_special: Option<ThemeStyle>,
209 #[serde(rename = "string.special.symbol")]
210 pub string_special_symbol: Option<ThemeStyle>,
211 pub tag: Option<ThemeStyle>,
212 #[serde(rename = "tag.doctype")]
213 pub tag_doctype: Option<ThemeStyle>,
214 #[serde(rename = "text.code.span")]
215 pub text_code_span: Option<ThemeStyle>,
216 #[serde(rename = "text.literal")]
217 pub text_literal: Option<ThemeStyle>,
218 pub title: Option<ThemeStyle>,
219 #[serde(rename = "type")]
220 pub type_: Option<ThemeStyle>,
221 pub variable: Option<ThemeStyle>,
222 #[serde(rename = "variable.special")]
223 pub variable_special: Option<ThemeStyle>,
224 pub variant: Option<ThemeStyle>,
225}
226
227impl SyntaxColors {
228 pub fn style(&self, name: &str) -> Option<HighlightStyle> {
229 if name.is_empty() {
230 return None;
231 }
232
233 let style = match name {
234 "attribute" => self.attribute,
235 "boolean" => self.boolean,
236 "comment" => self.comment,
237 "comment.doc" => self.comment_doc,
238 "constant" => self.constant,
239 "constructor" => self.constructor,
240 "embedded" => self.embedded,
241 "emphasis" => self.emphasis,
242 "emphasis.strong" => self.emphasis_strong,
243 "enum" => self.enum_,
244 "function" => self.function,
245 "hint" => self.hint,
246 "keyword" => self.keyword,
247 "label" => self.label,
248 "link_text" => self.link_text,
249 "link_uri" => self.link_uri,
250 "number" => self.number,
251 "operator" => self.operator,
252 "predictive" => self.predictive,
253 "preproc" => self.preproc,
254 "primary" => self.primary,
255 "property" => self.property,
256 "punctuation" => self.punctuation,
257 "punctuation.bracket" => self.punctuation_bracket,
258 "punctuation.delimiter" => self.punctuation_delimiter,
259 "punctuation.list_marker" => self.punctuation_list_marker,
260 "punctuation.special" => self.punctuation_special,
261 "string" => self.string,
262 "string.escape" => self.string_escape,
263 "string.regex" => self.string_regex,
264 "string.special" => self.string_special,
265 "string.special.symbol" => self.string_special_symbol,
266 "tag" => self.tag,
267 "tag.doctype" => self.tag_doctype,
268 "text.code.span" => self.text_code_span,
269 "text.literal" => self.text_literal,
270 "title" => self.title,
271 "type" => self.type_,
272 "variable" => self.variable,
273 "variable.special" => self.variable_special,
274 "variant" => self.variant,
275 _ => None,
276 }
277 .map(|s| s.into());
278
279 if style.is_some() {
280 style
281 } else if name.contains('.') {
282 name.split('.').next().and_then(|prefix| self.style(prefix))
283 } else {
284 None
285 }
286 }
287
288 pub fn style_for_index(&self, index: usize) -> Option<HighlightStyle> {
289 const HIGHLIGHT_NAMES: [&str; 41] = [
290 "attribute",
291 "boolean",
292 "comment",
293 "comment.doc",
294 "constant",
295 "constructor",
296 "embedded",
297 "emphasis",
298 "emphasis.strong",
299 "enum",
300 "function",
301 "hint",
302 "keyword",
303 "label",
304 "link_text",
305 "link_uri",
306 "number",
307 "operator",
308 "predictive",
309 "preproc",
310 "primary",
311 "property",
312 "punctuation",
313 "punctuation.bracket",
314 "punctuation.delimiter",
315 "punctuation.list_marker",
316 "punctuation.special",
317 "string",
318 "string.escape",
319 "string.regex",
320 "string.special",
321 "string.special.symbol",
322 "tag",
323 "tag.doctype",
324 "text.code.span",
325 "text.literal",
326 "title",
327 "type",
328 "variable",
329 "variable.special",
330 "variant",
331 ];
332
333 HIGHLIGHT_NAMES.get(index).and_then(|name| self.style(name))
334 }
335}
336
337#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
338pub struct StatusColors {
339 }
341
342impl StatusColors {
343 pub fn error(&self, _cx: &gpui::App) -> gpui::Hsla {
344 gpui::Hsla::default()
345 }
346
347 pub fn error_background(&self, _cx: &gpui::App) -> gpui::Hsla {
348 gpui::Hsla::default()
349 }
350
351 pub fn error_border(&self, _cx: &gpui::App) -> gpui::Hsla {
352 gpui::Hsla::default()
353 }
354
355 pub fn warning(&self, _cx: &gpui::App) -> gpui::Hsla {
356 gpui::Hsla::default()
357 }
358
359 pub fn warning_background(&self, _cx: &gpui::App) -> gpui::Hsla {
360 gpui::Hsla::default()
361 }
362
363 pub fn warning_border(&self, _cx: &gpui::App) -> gpui::Hsla {
364 gpui::Hsla::default()
365 }
366
367 pub fn info(&self, _cx: &gpui::App) -> gpui::Hsla {
368 gpui::Hsla::default()
369 }
370
371 pub fn info_background(&self, _cx: &gpui::App) -> gpui::Hsla {
372 gpui::Hsla::default()
373 }
374
375 pub fn info_border(&self, _cx: &gpui::App) -> gpui::Hsla {
376 gpui::Hsla::default()
377 }
378
379 pub fn success(&self, _cx: &gpui::App) -> gpui::Hsla {
380 gpui::Hsla::default()
381 }
382
383 pub fn success_background(&self, _cx: &gpui::App) -> gpui::Hsla {
384 gpui::Hsla::default()
385 }
386
387 pub fn success_border(&self, _cx: &gpui::App) -> gpui::Hsla {
388 gpui::Hsla::default()
389 }
390
391 pub fn hint(&self, _cx: &gpui::App) -> gpui::Hsla {
392 gpui::Hsla::default()
393 }
394
395 pub fn hint_background(&self, _cx: &gpui::App) -> gpui::Hsla {
396 gpui::Hsla::default()
397 }
398
399 pub fn hint_border(&self, _cx: &gpui::App) -> gpui::Hsla {
400 gpui::Hsla::default()
401 }
402}
403
404#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
405pub struct HighlightThemeStyle {
406 pub editor_background: Option<gpui::Hsla>,
407 pub editor_foreground: Option<gpui::Hsla>,
408 pub editor_active_line: Option<gpui::Hsla>,
409 pub editor_line_number: Option<gpui::Hsla>,
410 pub editor_active_line_number: Option<gpui::Hsla>,
411 pub editor_invisible: Option<gpui::Hsla>,
412 #[serde(rename = "editor.gutter.background")]
413 pub editor_gutter_background: Option<gpui::Hsla>,
414 #[serde(flatten)]
415 pub status: StatusColors,
416 #[serde(rename = "syntax")]
417 pub syntax: SyntaxColors,
418}
419
420#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
421pub struct HighlightTheme {
422 pub name: String,
423 #[serde(default)]
424 pub appearance: crate::ThemeMode,
425 pub style: HighlightThemeStyle,
426}
427
428impl std::ops::Deref for HighlightTheme {
429 type Target = SyntaxColors;
430
431 fn deref(&self) -> &Self::Target {
432 &self.style.syntax
433 }
434}
435
436impl HighlightTheme {
437 pub fn default_dark() -> std::sync::Arc<Self> {
438 use crate::DEFAULT_THEME_COLORS;
439 DEFAULT_THEME_COLORS[&crate::ThemeMode::Dark].1.clone()
440 }
441
442 pub fn default_light() -> std::sync::Arc<Self> {
443 use crate::DEFAULT_THEME_COLORS;
444 DEFAULT_THEME_COLORS[&crate::ThemeMode::Light].1.clone()
445 }
446}
447
448impl gpui_base::input::HighlightStyleResolver for HighlightTheme {
449 fn style(&self, name: &str) -> Option<HighlightStyle> {
450 self.style.syntax.style(name)
451 }
452}
453
454pub struct LanguageRegistry {
456 languages: Mutex<HashMap<SharedString, GrammarConfig>>,
457}
458
459impl LanguageRegistry {
460 pub fn singleton() -> &'static LazyLock<LanguageRegistry> {
461 static INSTANCE: LazyLock<LanguageRegistry> = LazyLock::new(|| LanguageRegistry {
462 languages: Mutex::new(HashMap::new()),
463 });
464 &INSTANCE
465 }
466
467 pub fn register(&self, lang: &str, config: &GrammarConfig) {
468 self.languages
469 .lock()
470 .unwrap()
471 .insert(lang.to_string().into(), config.clone());
472 }
473
474 pub(crate) fn editing_language_name(&self, name: &str) -> SharedString {
475 self.languages
476 .lock()
477 .unwrap()
478 .get_key_value(name)
479 .map(|(name, _)| name.clone())
480 .unwrap_or_else(|| super::language_name(name))
481 }
482
483 pub fn languages(&self) -> Vec<SharedString> {
484 self.languages.lock().unwrap().keys().cloned().collect()
485 }
486
487 pub fn language(&self, name: &str) -> Option<GrammarConfig> {
488 self.languages.lock().unwrap().get(name).cloned()
489 }
490}
491
492#[cfg(test)]
493mod registry_compat_tests {
494 use super::*;
495
496 #[test]
497 fn registrations_preserve_exact_names_without_alias_fallback() {
498 let registry = LanguageRegistry {
499 languages: Mutex::new(HashMap::new()),
500 };
501 registry.register(
502 "json",
503 &LanguageConfig {
504 name: "canonical".into(),
505 },
506 );
507 assert!(registry.language("jsonc").is_none());
508 registry.register(
509 "jsonc",
510 &LanguageConfig {
511 name: "custom alias".into(),
512 },
513 );
514 registry.register(
515 "JSON",
516 &LanguageConfig {
517 name: "custom uppercase".into(),
518 },
519 );
520 assert_eq!(registry.language("json").unwrap().name, "canonical");
521 assert_eq!(registry.language("jsonc").unwrap().name, "custom alias");
522 assert_eq!(registry.language("JSON").unwrap().name, "custom uppercase");
523 assert!(registry.language("Json").is_none());
524 let mut names = registry.languages();
525 names.sort();
526 assert_eq!(names, vec!["JSON", "json", "jsonc"]);
527 assert_eq!(registry.editing_language_name("jsonc"), "jsonc");
528 assert_eq!(registry.editing_language_name("JSON"), "JSON");
529 assert_eq!(registry.editing_language_name("pyi"), "python");
530 }
531}