1use crate::HighlightMap;
2use anyhow::Result;
3use gpui::AppContext;
4use parking_lot::Mutex;
5use serde::Deserialize;
6use std::{collections::HashSet, path::Path, str, sync::Arc};
7use theme::SyntaxTheme;
8use tree_sitter::{Language as Grammar, Query};
9pub use tree_sitter::{Parser, Tree};
10
11#[derive(Default, Deserialize)]
12pub struct LanguageConfig {
13 pub name: String,
14 pub path_suffixes: Vec<String>,
15 pub brackets: Vec<BracketPair>,
16 pub language_server: Option<LanguageServerConfig>,
17}
18
19#[derive(Deserialize)]
20pub struct LanguageServerConfig {
21 pub binary: String,
22 pub disk_based_diagnostic_sources: HashSet<String>,
23}
24
25#[derive(Clone, Debug, Deserialize)]
26pub struct BracketPair {
27 pub start: String,
28 pub end: String,
29 pub close: bool,
30 pub newline: bool,
31}
32
33pub struct Language {
34 pub(crate) config: LanguageConfig,
35 pub(crate) grammar: Grammar,
36 pub(crate) highlights_query: Query,
37 pub(crate) brackets_query: Query,
38 pub(crate) indents_query: Query,
39 pub(crate) highlight_map: Mutex<HighlightMap>,
40}
41
42#[derive(Default)]
43pub struct LanguageRegistry {
44 languages: Vec<Arc<Language>>,
45}
46
47impl LanguageRegistry {
48 pub fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn add(&mut self, language: Arc<Language>) {
53 self.languages.push(language);
54 }
55
56 pub fn set_theme(&self, theme: &SyntaxTheme) {
57 for language in &self.languages {
58 language.set_theme(theme);
59 }
60 }
61
62 pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
63 self.languages
64 .iter()
65 .find(|language| language.name() == name)
66 }
67
68 pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
69 let path = path.as_ref();
70 let filename = path.file_name().and_then(|name| name.to_str());
71 let extension = path.extension().and_then(|name| name.to_str());
72 let path_suffixes = [extension, filename];
73 self.languages.iter().find(|language| {
74 language
75 .config
76 .path_suffixes
77 .iter()
78 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
79 })
80 }
81}
82
83impl Language {
84 pub fn new(config: LanguageConfig, grammar: Grammar) -> Self {
85 Self {
86 config,
87 brackets_query: Query::new(grammar, "").unwrap(),
88 highlights_query: Query::new(grammar, "").unwrap(),
89 indents_query: Query::new(grammar, "").unwrap(),
90 grammar,
91 highlight_map: Default::default(),
92 }
93 }
94
95 pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
96 self.highlights_query = Query::new(self.grammar, source)?;
97 Ok(self)
98 }
99
100 pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
101 self.brackets_query = Query::new(self.grammar, source)?;
102 Ok(self)
103 }
104
105 pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
106 self.indents_query = Query::new(self.grammar, source)?;
107 Ok(self)
108 }
109
110 pub fn name(&self) -> &str {
111 self.config.name.as_str()
112 }
113
114 pub fn start_server(
115 &self,
116 root_path: &Path,
117 cx: &AppContext,
118 ) -> Result<Option<Arc<lsp::LanguageServer>>> {
119 if let Some(config) = &self.config.language_server {
120 const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
121 let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
122 cx.platform()
123 .path_for_resource(Some(&config.binary), None)?
124 } else {
125 Path::new(&config.binary).to_path_buf()
126 };
127 lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
128 } else {
129 Ok(None)
130 }
131 }
132
133 pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
134 self.config
135 .language_server
136 .as_ref()
137 .map(|config| &config.disk_based_diagnostic_sources)
138 }
139
140 pub fn brackets(&self) -> &[BracketPair] {
141 &self.config.brackets
142 }
143
144 pub fn highlight_map(&self) -> HighlightMap {
145 self.highlight_map.lock().clone()
146 }
147
148 pub fn set_theme(&self, theme: &SyntaxTheme) {
149 *self.highlight_map.lock() =
150 HighlightMap::new(self.highlights_query.capture_names(), theme);
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_select_language() {
160 let grammar = tree_sitter_rust::language();
161 let registry = LanguageRegistry {
162 languages: vec![
163 Arc::new(Language::new(
164 LanguageConfig {
165 name: "Rust".to_string(),
166 path_suffixes: vec!["rs".to_string()],
167 ..Default::default()
168 },
169 grammar,
170 )),
171 Arc::new(Language::new(
172 LanguageConfig {
173 name: "Make".to_string(),
174 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
175 ..Default::default()
176 },
177 grammar,
178 )),
179 ],
180 };
181
182 // matching file extension
183 assert_eq!(
184 registry.select_language("zed/lib.rs").map(|l| l.name()),
185 Some("Rust")
186 );
187 assert_eq!(
188 registry.select_language("zed/lib.mk").map(|l| l.name()),
189 Some("Make")
190 );
191
192 // matching filename
193 assert_eq!(
194 registry.select_language("zed/Makefile").map(|l| l.name()),
195 Some("Make")
196 );
197
198 // matching suffix that is not the full file extension or filename
199 assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
200 assert_eq!(
201 registry.select_language("zed/a.cars").map(|l| l.name()),
202 None
203 );
204 assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
205 }
206}