1mod buffer;
2mod diagnostic_set;
3mod highlight_map;
4mod outline;
5pub mod proto;
6#[cfg(test)]
7mod tests;
8
9use anyhow::{anyhow, Result};
10use collections::HashSet;
11use gpui::AppContext;
12use highlight_map::HighlightMap;
13use lazy_static::lazy_static;
14use parking_lot::Mutex;
15use serde::Deserialize;
16use std::{cell::RefCell, ops::Range, path::Path, str, sync::Arc};
17use theme::SyntaxTheme;
18use tree_sitter::{self, Query};
19
20pub use buffer::Operation;
21pub use buffer::*;
22pub use diagnostic_set::DiagnosticEntry;
23pub use outline::{Outline, OutlineItem};
24pub use tree_sitter::{Parser, Tree};
25
26thread_local! {
27 static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
28}
29
30lazy_static! {
31 pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
32 LanguageConfig {
33 name: "Plain Text".to_string(),
34 path_suffixes: Default::default(),
35 brackets: Default::default(),
36 line_comment: None,
37 language_server: None,
38 },
39 None,
40 ));
41}
42
43pub trait ToLspPosition {
44 fn to_lsp_position(self) -> lsp::Position;
45}
46
47pub trait LspPostProcessor: 'static + Send + Sync {
48 fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
49 fn label_for_completion(
50 &self,
51 _: &lsp::CompletionItem,
52 _: &Language,
53 ) -> Option<CompletionLabel> {
54 None
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct CompletionLabel {
60 pub text: String,
61 pub runs: Vec<(Range<usize>, HighlightId)>,
62 pub filter_range: Range<usize>,
63 pub left_aligned_len: usize,
64}
65
66#[derive(Default, Deserialize)]
67pub struct LanguageConfig {
68 pub name: String,
69 pub path_suffixes: Vec<String>,
70 pub brackets: Vec<BracketPair>,
71 pub line_comment: Option<String>,
72 pub language_server: Option<LanguageServerConfig>,
73}
74
75#[derive(Default, Deserialize)]
76pub struct LanguageServerConfig {
77 pub binary: String,
78 pub disk_based_diagnostic_sources: HashSet<String>,
79 pub disk_based_diagnostics_progress_token: Option<String>,
80 #[cfg(any(test, feature = "test-support"))]
81 #[serde(skip)]
82 pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
83}
84
85#[derive(Clone, Debug, Deserialize)]
86pub struct BracketPair {
87 pub start: String,
88 pub end: String,
89 pub close: bool,
90 pub newline: bool,
91}
92
93pub struct Language {
94 pub(crate) config: LanguageConfig,
95 pub(crate) grammar: Option<Arc<Grammar>>,
96 pub(crate) lsp_post_processor: Option<Box<dyn LspPostProcessor>>,
97}
98
99pub struct Grammar {
100 pub(crate) ts_language: tree_sitter::Language,
101 pub(crate) highlights_query: Query,
102 pub(crate) brackets_query: Query,
103 pub(crate) indents_query: Query,
104 pub(crate) outline_query: Query,
105 pub(crate) highlight_map: Mutex<HighlightMap>,
106}
107
108#[derive(Default)]
109pub struct LanguageRegistry {
110 languages: Vec<Arc<Language>>,
111}
112
113impl LanguageRegistry {
114 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn add(&mut self, language: Arc<Language>) {
119 self.languages.push(language);
120 }
121
122 pub fn set_theme(&self, theme: &SyntaxTheme) {
123 for language in &self.languages {
124 language.set_theme(theme);
125 }
126 }
127
128 pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
129 self.languages
130 .iter()
131 .find(|language| language.name() == name)
132 }
133
134 pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
135 let path = path.as_ref();
136 let filename = path.file_name().and_then(|name| name.to_str());
137 let extension = path.extension().and_then(|name| name.to_str());
138 let path_suffixes = [extension, filename];
139 self.languages.iter().find(|language| {
140 language
141 .config
142 .path_suffixes
143 .iter()
144 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
145 })
146 }
147}
148
149impl Language {
150 pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
151 Self {
152 config,
153 grammar: ts_language.map(|ts_language| {
154 Arc::new(Grammar {
155 brackets_query: Query::new(ts_language, "").unwrap(),
156 highlights_query: Query::new(ts_language, "").unwrap(),
157 indents_query: Query::new(ts_language, "").unwrap(),
158 outline_query: Query::new(ts_language, "").unwrap(),
159 ts_language,
160 highlight_map: Default::default(),
161 })
162 }),
163 lsp_post_processor: None,
164 }
165 }
166
167 pub fn with_highlights_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.highlights_query = Query::new(grammar.ts_language, source)?;
174 Ok(self)
175 }
176
177 pub fn with_brackets_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.brackets_query = Query::new(grammar.ts_language, source)?;
184 Ok(self)
185 }
186
187 pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
188 let grammar = self
189 .grammar
190 .as_mut()
191 .and_then(Arc::get_mut)
192 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
193 grammar.indents_query = Query::new(grammar.ts_language, source)?;
194 Ok(self)
195 }
196
197 pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
198 let grammar = self
199 .grammar
200 .as_mut()
201 .and_then(Arc::get_mut)
202 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
203 grammar.outline_query = Query::new(grammar.ts_language, source)?;
204 Ok(self)
205 }
206
207 pub fn with_lsp_post_processor(mut self, processor: impl LspPostProcessor) -> Self {
208 self.lsp_post_processor = Some(Box::new(processor));
209 self
210 }
211
212 pub fn name(&self) -> &str {
213 self.config.name.as_str()
214 }
215
216 pub fn line_comment_prefix(&self) -> Option<&str> {
217 self.config.line_comment.as_deref()
218 }
219
220 pub fn start_server(
221 &self,
222 root_path: &Path,
223 cx: &AppContext,
224 ) -> Result<Option<Arc<lsp::LanguageServer>>> {
225 if let Some(config) = &self.config.language_server {
226 #[cfg(any(test, feature = "test-support"))]
227 if let Some((server, started)) = &config.fake_server {
228 started.store(true, std::sync::atomic::Ordering::SeqCst);
229 return Ok(Some(server.clone()));
230 }
231
232 const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
233 let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
234 cx.platform()
235 .path_for_resource(Some(&config.binary), None)?
236 } else {
237 Path::new(&config.binary).to_path_buf()
238 };
239 lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
240 } else {
241 Ok(None)
242 }
243 }
244
245 pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
246 self.config
247 .language_server
248 .as_ref()
249 .map(|config| &config.disk_based_diagnostic_sources)
250 }
251
252 pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
253 self.config
254 .language_server
255 .as_ref()
256 .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
257 }
258
259 pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
260 if let Some(processor) = self.lsp_post_processor.as_ref() {
261 processor.process_diagnostics(diagnostics);
262 }
263 }
264
265 pub fn label_for_completion(
266 &self,
267 completion: &lsp::CompletionItem,
268 ) -> Option<CompletionLabel> {
269 self.lsp_post_processor
270 .as_ref()?
271 .label_for_completion(completion, self)
272 }
273
274 pub fn highlight_text<'a>(
275 &'a self,
276 text: &'a Rope,
277 range: Range<usize>,
278 ) -> Vec<(Range<usize>, HighlightId)> {
279 let mut result = Vec::new();
280 if let Some(grammar) = &self.grammar {
281 let tree = grammar.parse_text(text, None);
282 let mut offset = 0;
283 for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![])
284 {
285 let end_offset = offset + chunk.text.len();
286 if let Some(highlight_id) = chunk.highlight_id {
287 result.push((offset..end_offset, highlight_id));
288 }
289 offset = end_offset;
290 }
291 }
292 result
293 }
294
295 pub fn brackets(&self) -> &[BracketPair] {
296 &self.config.brackets
297 }
298
299 pub fn set_theme(&self, theme: &SyntaxTheme) {
300 if let Some(grammar) = self.grammar.as_ref() {
301 *grammar.highlight_map.lock() =
302 HighlightMap::new(grammar.highlights_query.capture_names(), theme);
303 }
304 }
305
306 pub fn grammar(&self) -> Option<&Arc<Grammar>> {
307 self.grammar.as_ref()
308 }
309}
310
311impl Grammar {
312 fn parse_text(&self, text: &Rope, old_tree: Option<Tree>) -> Tree {
313 PARSER.with(|parser| {
314 let mut parser = parser.borrow_mut();
315 parser
316 .set_language(self.ts_language)
317 .expect("incompatible grammar");
318 let mut chunks = text.chunks_in_range(0..text.len());
319 parser
320 .parse_with(
321 &mut move |offset, _| {
322 chunks.seek(offset);
323 chunks.next().unwrap_or("").as_bytes()
324 },
325 old_tree.as_ref(),
326 )
327 .unwrap()
328 })
329 }
330
331 pub fn highlight_map(&self) -> HighlightMap {
332 self.highlight_map.lock().clone()
333 }
334
335 pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
336 let capture_id = self.highlights_query.capture_index_for_name(name)?;
337 Some(self.highlight_map.lock().get(capture_id))
338 }
339}
340
341impl CompletionLabel {
342 pub fn plain(completion: &lsp::CompletionItem) -> Self {
343 let mut result = Self {
344 text: completion.label.clone(),
345 runs: Vec::new(),
346 left_aligned_len: completion.label.len(),
347 filter_range: 0..completion.label.len(),
348 };
349 if let Some(filter_text) = &completion.filter_text {
350 if let Some(ix) = completion.label.find(filter_text) {
351 result.filter_range = ix..ix + filter_text.len();
352 }
353 }
354 result
355 }
356}
357
358#[cfg(any(test, feature = "test-support"))]
359impl LanguageServerConfig {
360 pub async fn fake(cx: &gpui::TestAppContext) -> (Self, lsp::FakeLanguageServer) {
361 Self::fake_with_capabilities(Default::default(), cx).await
362 }
363
364 pub async fn fake_with_capabilities(
365 capabilites: lsp::ServerCapabilities,
366 cx: &gpui::TestAppContext,
367 ) -> (Self, lsp::FakeLanguageServer) {
368 let (server, fake) = lsp::LanguageServer::fake_with_capabilities(capabilites, cx).await;
369 fake.started
370 .store(false, std::sync::atomic::Ordering::SeqCst);
371 let started = fake.started.clone();
372 (
373 Self {
374 fake_server: Some((server, started)),
375 disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
376 ..Default::default()
377 },
378 fake,
379 )
380 }
381}
382
383impl ToLspPosition for PointUtf16 {
384 fn to_lsp_position(self) -> lsp::Position {
385 lsp::Position::new(self.row, self.column)
386 }
387}
388
389pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 {
390 PointUtf16::new(point.line, point.character)
391}
392
393pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
394 let start = PointUtf16::new(range.start.line, range.start.character);
395 let end = PointUtf16::new(range.end.line, range.end.character);
396 start..end
397}