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