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