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