1use crate::{HighlightMap};
2use parking_lot::Mutex;
3use serde::Deserialize;
4use std::{path::Path, str, sync::Arc};
5use tree_sitter::{Language as Grammar, Query};
6pub use tree_sitter::{Parser, Tree};
7use theme::SyntaxTheme;
8
9#[derive(Default, Deserialize)]
10pub struct LanguageConfig {
11 pub name: String,
12 pub path_suffixes: Vec<String>,
13}
14
15#[derive(Deserialize)]
16pub struct BracketPair {
17 pub start: String,
18 pub end: String,
19}
20
21pub struct Language {
22 pub config: LanguageConfig,
23 pub grammar: Grammar,
24 pub highlight_query: Query,
25 pub brackets_query: Query,
26 pub highlight_map: Mutex<HighlightMap>,
27}
28
29#[derive(Default)]
30pub struct LanguageRegistry {
31 languages: Vec<Arc<Language>>,
32}
33
34impl LanguageRegistry {
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn add(&mut self, language: Arc<Language>) {
40 self.languages.push(language);
41 }
42
43 pub fn set_theme(&self, theme: &SyntaxTheme) {
44 for language in &self.languages {
45 language.set_theme(theme);
46 }
47 }
48
49 pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
50 let path = path.as_ref();
51 let filename = path.file_name().and_then(|name| name.to_str());
52 let extension = path.extension().and_then(|name| name.to_str());
53 let path_suffixes = [extension, filename];
54 self.languages.iter().find(|language| {
55 language
56 .config
57 .path_suffixes
58 .iter()
59 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
60 })
61 }
62}
63
64impl Language {
65 pub fn name(&self) -> &str {
66 self.config.name.as_str()
67 }
68
69 pub fn highlight_map(&self) -> HighlightMap {
70 self.highlight_map.lock().clone()
71 }
72
73 pub fn set_theme(&self, theme: &SyntaxTheme) {
74 *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme);
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn test_select_language() {
84 let grammar = tree_sitter_rust::language();
85 let registry = LanguageRegistry {
86 languages: vec![
87 Arc::new(Language {
88 config: LanguageConfig {
89 name: "Rust".to_string(),
90 path_suffixes: vec!["rs".to_string()],
91 ..Default::default()
92 },
93 grammar,
94 highlight_query: Query::new(grammar, "").unwrap(),
95 brackets_query: Query::new(grammar, "").unwrap(),
96 highlight_map: Default::default(),
97 }),
98 Arc::new(Language {
99 config: LanguageConfig {
100 name: "Make".to_string(),
101 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
102 ..Default::default()
103 },
104 grammar,
105 highlight_query: Query::new(grammar, "").unwrap(),
106 brackets_query: Query::new(grammar, "").unwrap(),
107 highlight_map: Default::default(),
108 }),
109 ],
110 };
111
112 // matching file extension
113 assert_eq!(
114 registry.select_language("zed/lib.rs").map(|l| l.name()),
115 Some("Rust")
116 );
117 assert_eq!(
118 registry.select_language("zed/lib.mk").map(|l| l.name()),
119 Some("Make")
120 );
121
122 // matching filename
123 assert_eq!(
124 registry.select_language("zed/Makefile").map(|l| l.name()),
125 Some("Make")
126 );
127
128 // matching suffix that is not the full file extension or filename
129 assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
130 assert_eq!(
131 registry.select_language("zed/a.cars").map(|l| l.name()),
132 None
133 );
134 assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
135 }
136}