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