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