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
42#[derive(Default, Deserialize)]
43pub struct LanguageConfig {
44 pub name: String,
45 pub path_suffixes: Vec<String>,
46 pub brackets: Vec<BracketPair>,
47 pub line_comment: Option<String>,
48 pub language_server: Option<LanguageServerConfig>,
49}
50
51#[derive(Default, Deserialize)]
52pub struct LanguageServerConfig {
53 pub binary: String,
54 pub disk_based_diagnostic_sources: HashSet<String>,
55 pub disk_based_diagnostics_progress_token: Option<String>,
56 #[cfg(any(test, feature = "test-support"))]
57 #[serde(skip)]
58 pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
59}
60
61#[derive(Clone, Debug, Deserialize)]
62pub struct BracketPair {
63 pub start: String,
64 pub end: String,
65 pub close: bool,
66 pub newline: bool,
67}
68
69pub struct Language {
70 pub(crate) config: LanguageConfig,
71 pub(crate) grammar: Option<Arc<Grammar>>,
72}
73
74pub struct Grammar {
75 pub(crate) ts_language: tree_sitter::Language,
76 pub(crate) highlights_query: Query,
77 pub(crate) brackets_query: Query,
78 pub(crate) indents_query: Query,
79 pub(crate) outline_query: Query,
80 pub(crate) highlight_map: Mutex<HighlightMap>,
81}
82
83#[derive(Default)]
84pub struct LanguageRegistry {
85 languages: Vec<Arc<Language>>,
86}
87
88impl LanguageRegistry {
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn add(&mut self, language: Arc<Language>) {
94 self.languages.push(language);
95 }
96
97 pub fn set_theme(&self, theme: &SyntaxTheme) {
98 for language in &self.languages {
99 language.set_theme(theme);
100 }
101 }
102
103 pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
104 self.languages
105 .iter()
106 .find(|language| language.name() == name)
107 }
108
109 pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
110 let path = path.as_ref();
111 let filename = path.file_name().and_then(|name| name.to_str());
112 let extension = path.extension().and_then(|name| name.to_str());
113 let path_suffixes = [extension, filename];
114 self.languages.iter().find(|language| {
115 language
116 .config
117 .path_suffixes
118 .iter()
119 .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
120 })
121 }
122}
123
124impl Language {
125 pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
126 Self {
127 config,
128 grammar: ts_language.map(|ts_language| {
129 Arc::new(Grammar {
130 brackets_query: Query::new(ts_language, "").unwrap(),
131 highlights_query: Query::new(ts_language, "").unwrap(),
132 indents_query: Query::new(ts_language, "").unwrap(),
133 outline_query: Query::new(ts_language, "").unwrap(),
134 ts_language,
135 highlight_map: Default::default(),
136 })
137 }),
138 }
139 }
140
141 pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
142 let grammar = self
143 .grammar
144 .as_mut()
145 .and_then(Arc::get_mut)
146 .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
147 grammar.highlights_query = Query::new(grammar.ts_language, source)?;
148 Ok(self)
149 }
150
151 pub fn with_brackets_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.brackets_query = Query::new(grammar.ts_language, source)?;
158 Ok(self)
159 }
160
161 pub fn with_indents_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.indents_query = Query::new(grammar.ts_language, source)?;
168 Ok(self)
169 }
170
171 pub fn with_outline_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.outline_query = Query::new(grammar.ts_language, source)?;
178 Ok(self)
179 }
180
181 pub fn name(&self) -> &str {
182 self.config.name.as_str()
183 }
184
185 pub fn line_comment_prefix(&self) -> Option<&str> {
186 self.config.line_comment.as_deref()
187 }
188
189 pub fn start_server(
190 &self,
191 root_path: &Path,
192 cx: &AppContext,
193 ) -> Result<Option<Arc<lsp::LanguageServer>>> {
194 if let Some(config) = &self.config.language_server {
195 #[cfg(any(test, feature = "test-support"))]
196 if let Some((server, started)) = &config.fake_server {
197 started.store(true, std::sync::atomic::Ordering::SeqCst);
198 return Ok(Some(server.clone()));
199 }
200
201 const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
202 let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
203 cx.platform()
204 .path_for_resource(Some(&config.binary), None)?
205 } else {
206 Path::new(&config.binary).to_path_buf()
207 };
208 lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
209 } else {
210 Ok(None)
211 }
212 }
213
214 pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
215 self.config
216 .language_server
217 .as_ref()
218 .map(|config| &config.disk_based_diagnostic_sources)
219 }
220
221 pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
222 self.config
223 .language_server
224 .as_ref()
225 .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
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 disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
259 ..Default::default()
260 },
261 fake,
262 )
263 }
264}
265
266impl ToPointUtf16 for lsp::Position {
267 fn to_point_utf16(self) -> PointUtf16 {
268 PointUtf16::new(self.line, self.character)
269 }
270}
271
272pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
273 let start = PointUtf16::new(range.start.line, range.start.character);
274 let end = PointUtf16::new(range.end.line, range.end.character);
275 start..end
276}