1mod buffer;
2mod diagnostic_set;
3mod highlight_map;
4pub mod proto;
5#[cfg(test)]
6mod tests;
7
8use anyhow::{anyhow, Result};
9use async_trait::async_trait;
10pub use buffer::Operation;
11pub use buffer::*;
12use collections::{HashMap, HashSet};
13pub use diagnostic_set::DiagnosticEntry;
14use gpui::AppContext;
15use highlight_map::HighlightMap;
16use lazy_static::lazy_static;
17use parking_lot::Mutex;
18use serde::Deserialize;
19use std::{path::Path, str, sync::Arc};
20use theme::SyntaxTheme;
21use tree_sitter::{self, Query};
22pub use tree_sitter::{Parser, Tree};
23
24lazy_static! {
25 pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
26 LanguageConfig {
27 name: "Plain Text".to_string(),
28 path_suffixes: Default::default(),
29 brackets: Default::default(),
30 line_comment: None,
31 language_server: None,
32 },
33 None,
34 ));
35}
36
37#[derive(Default, Deserialize)]
38pub struct LanguageConfig {
39 pub name: String,
40 pub path_suffixes: Vec<String>,
41 pub brackets: Vec<BracketPair>,
42 pub line_comment: Option<String>,
43 pub language_server: Option<LanguageServerConfig>,
44}
45
46#[derive(Default, Deserialize)]
47pub struct LanguageServerConfig {
48 pub binary: String,
49 pub disk_based_diagnostic_sources: HashSet<String>,
50 #[cfg(any(test, feature = "test-support"))]
51 #[serde(skip)]
52 pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
53}
54
55#[derive(Clone, Debug, Deserialize)]
56pub struct BracketPair {
57 pub start: String,
58 pub end: String,
59 pub close: bool,
60 pub newline: bool,
61}
62
63#[async_trait]
64pub trait DiagnosticProvider: 'static + Send + Sync {
65 async fn diagnose(
66 &self,
67 path: Arc<Path>,
68 ) -> Result<HashMap<Arc<Path>, Vec<DiagnosticEntry<usize>>>>;
69}
70
71pub struct Language {
72 pub(crate) config: LanguageConfig,
73 pub(crate) grammar: Option<Arc<Grammar>>,
74 pub(crate) diagnostic_provider: Option<Arc<dyn DiagnosticProvider>>,
75}
76
77pub struct Grammar {
78 pub(crate) ts_language: tree_sitter::Language,
79 pub(crate) highlights_query: Query,
80 pub(crate) brackets_query: Query,
81 pub(crate) indents_query: Query,
82 pub(crate) highlight_map: Mutex<HighlightMap>,
83}
84
85#[derive(Default)]
86pub struct LanguageRegistry {
87 languages: Vec<Arc<Language>>,
88}
89
90impl LanguageRegistry {
91 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn add(&mut self, language: Arc<Language>) {
96 self.languages.push(language);
97 }
98
99 pub fn set_theme(&self, theme: &SyntaxTheme) {
100 for language in &self.languages {
101 language.set_theme(theme);
102 }
103 }
104
105 pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
106 self.languages
107 .iter()
108 .find(|language| language.name() == name)
109 }
110
111 pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
112 let path = path.as_ref();
113 let filename = path.file_name().and_then(|name| name.to_str());
114 let extension = path.extension().and_then(|name| name.to_str());
115 let path_suffixes = [extension, filename];
116 self.languages.iter().find(|language| {
117 language
118 .config
119 .path_suffixes
120 .iter()
121 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
122 })
123 }
124}
125
126impl Language {
127 pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
128 Self {
129 config,
130 grammar: ts_language.map(|ts_language| {
131 Arc::new(Grammar {
132 brackets_query: Query::new(ts_language, "").unwrap(),
133 highlights_query: Query::new(ts_language, "").unwrap(),
134 indents_query: Query::new(ts_language, "").unwrap(),
135 ts_language,
136 highlight_map: Default::default(),
137 })
138 }),
139 diagnostic_provider: None,
140 }
141 }
142
143 pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
144 let grammar = self
145 .grammar
146 .as_mut()
147 .and_then(Arc::get_mut)
148 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
149 grammar.highlights_query = Query::new(grammar.ts_language, source)?;
150 Ok(self)
151 }
152
153 pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
154 let grammar = self
155 .grammar
156 .as_mut()
157 .and_then(Arc::get_mut)
158 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
159 grammar.brackets_query = Query::new(grammar.ts_language, source)?;
160 Ok(self)
161 }
162
163 pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
164 let grammar = self
165 .grammar
166 .as_mut()
167 .and_then(Arc::get_mut)
168 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
169 grammar.indents_query = Query::new(grammar.ts_language, source)?;
170 Ok(self)
171 }
172
173 pub fn with_diagnostic_provider(mut self, source: impl DiagnosticProvider) -> Self {
174 self.diagnostic_provider = Some(Arc::new(source));
175 self
176 }
177
178 pub fn name(&self) -> &str {
179 self.config.name.as_str()
180 }
181
182 pub fn line_comment_prefix(&self) -> Option<&str> {
183 self.config.line_comment.as_deref()
184 }
185
186 pub fn start_server(
187 &self,
188 root_path: &Path,
189 cx: &AppContext,
190 ) -> Result<Option<Arc<lsp::LanguageServer>>> {
191 if let Some(config) = &self.config.language_server {
192 #[cfg(any(test, feature = "test-support"))]
193 if let Some((server, started)) = &config.fake_server {
194 started.store(true, std::sync::atomic::Ordering::SeqCst);
195 return Ok(Some(server.clone()));
196 }
197
198 const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
199 let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
200 cx.platform()
201 .path_for_resource(Some(&config.binary), None)?
202 } else {
203 Path::new(&config.binary).to_path_buf()
204 };
205 lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
206 } else {
207 Ok(None)
208 }
209 }
210
211 pub fn diagnostic_provider(&self) -> Option<&Arc<dyn DiagnosticProvider>> {
212 self.diagnostic_provider.as_ref()
213 }
214
215 pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
216 self.config
217 .language_server
218 .as_ref()
219 .map(|config| &config.disk_based_diagnostic_sources)
220 }
221
222 pub fn brackets(&self) -> &[BracketPair] {
223 &self.config.brackets
224 }
225
226 pub fn set_theme(&self, theme: &SyntaxTheme) {
227 if let Some(grammar) = self.grammar.as_ref() {
228 *grammar.highlight_map.lock() =
229 HighlightMap::new(grammar.highlights_query.capture_names(), theme);
230 }
231 }
232}
233
234impl Grammar {
235 pub fn highlight_map(&self) -> HighlightMap {
236 self.highlight_map.lock().clone()
237 }
238}
239
240#[cfg(any(test, feature = "test-support"))]
241impl LanguageServerConfig {
242 pub async fn fake(
243 executor: Arc<gpui::executor::Background>,
244 ) -> (Self, lsp::FakeLanguageServer) {
245 let (server, fake) = lsp::LanguageServer::fake(executor).await;
246 fake.started
247 .store(false, std::sync::atomic::Ordering::SeqCst);
248 let started = fake.started.clone();
249 (
250 Self {
251 fake_server: Some((server, started)),
252 ..Default::default()
253 },
254 fake,
255 )
256 }
257}