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