language.rs

  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}