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