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