language.rs

  1use crate::HighlightMap;
  2use anyhow::Result;
  3use gpui::{executor::Background, AppContext};
  4use lsp::LanguageServer;
  5use parking_lot::Mutex;
  6use serde::Deserialize;
  7use std::{collections::HashSet, path::Path, str, sync::Arc};
  8use theme::SyntaxTheme;
  9use tree_sitter::{Language as Grammar, Query};
 10pub use tree_sitter::{Parser, Tree};
 11
 12#[derive(Default, Deserialize)]
 13pub struct LanguageConfig {
 14    pub name: String,
 15    pub path_suffixes: Vec<String>,
 16    pub brackets: Vec<BracketPair>,
 17    pub line_comment: Option<String>,
 18    pub language_server: Option<LanguageServerConfig>,
 19}
 20
 21#[derive(Default, Deserialize)]
 22pub struct LanguageServerConfig {
 23    pub binary: String,
 24    pub disk_based_diagnostic_sources: HashSet<String>,
 25    #[cfg(any(test, feature = "test-support"))]
 26    #[serde(skip)]
 27    pub fake_server: Option<(Arc<LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
 28}
 29
 30#[derive(Clone, Debug, Deserialize)]
 31pub struct BracketPair {
 32    pub start: String,
 33    pub end: String,
 34    pub close: bool,
 35    pub newline: bool,
 36}
 37
 38pub struct Language {
 39    pub(crate) config: LanguageConfig,
 40    pub(crate) grammar: Grammar,
 41    pub(crate) highlights_query: Query,
 42    pub(crate) brackets_query: Query,
 43    pub(crate) indents_query: Query,
 44    pub(crate) highlight_map: Mutex<HighlightMap>,
 45}
 46
 47#[derive(Default)]
 48pub struct LanguageRegistry {
 49    languages: Vec<Arc<Language>>,
 50}
 51
 52impl LanguageRegistry {
 53    pub fn new() -> Self {
 54        Self::default()
 55    }
 56
 57    pub fn add(&mut self, language: Arc<Language>) {
 58        self.languages.push(language);
 59    }
 60
 61    pub fn set_theme(&self, theme: &SyntaxTheme) {
 62        for language in &self.languages {
 63            language.set_theme(theme);
 64        }
 65    }
 66
 67    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
 68        self.languages
 69            .iter()
 70            .find(|language| language.name() == name)
 71    }
 72
 73    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
 74        let path = path.as_ref();
 75        let filename = path.file_name().and_then(|name| name.to_str());
 76        let extension = path.extension().and_then(|name| name.to_str());
 77        let path_suffixes = [extension, filename];
 78        self.languages.iter().find(|language| {
 79            language
 80                .config
 81                .path_suffixes
 82                .iter()
 83                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
 84        })
 85    }
 86}
 87
 88impl Language {
 89    pub fn new(config: LanguageConfig, grammar: Grammar) -> Self {
 90        Self {
 91            config,
 92            brackets_query: Query::new(grammar, "").unwrap(),
 93            highlights_query: Query::new(grammar, "").unwrap(),
 94            indents_query: Query::new(grammar, "").unwrap(),
 95            grammar,
 96            highlight_map: Default::default(),
 97        }
 98    }
 99
100    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
101        self.highlights_query = Query::new(self.grammar, source)?;
102        Ok(self)
103    }
104
105    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
106        self.brackets_query = Query::new(self.grammar, source)?;
107        Ok(self)
108    }
109
110    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
111        self.indents_query = Query::new(self.grammar, source)?;
112        Ok(self)
113    }
114
115    pub fn name(&self) -> &str {
116        self.config.name.as_str()
117    }
118
119    pub fn line_comment_prefix(&self) -> Option<&str> {
120        self.config.line_comment.as_deref()
121    }
122
123    pub fn start_server(
124        &self,
125        root_path: &Path,
126        cx: &AppContext,
127    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
128        if let Some(config) = &self.config.language_server {
129            #[cfg(any(test, feature = "test-support"))]
130            if let Some((server, started)) = &config.fake_server {
131                started.store(true, std::sync::atomic::Ordering::SeqCst);
132                return Ok(Some(server.clone()));
133            }
134
135            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
136            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
137                cx.platform()
138                    .path_for_resource(Some(&config.binary), None)?
139            } else {
140                Path::new(&config.binary).to_path_buf()
141            };
142            lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
143        } else {
144            Ok(None)
145        }
146    }
147
148    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
149        self.config
150            .language_server
151            .as_ref()
152            .map(|config| &config.disk_based_diagnostic_sources)
153    }
154
155    pub fn brackets(&self) -> &[BracketPair] {
156        &self.config.brackets
157    }
158
159    pub fn highlight_map(&self) -> HighlightMap {
160        self.highlight_map.lock().clone()
161    }
162
163    pub fn set_theme(&self, theme: &SyntaxTheme) {
164        *self.highlight_map.lock() =
165            HighlightMap::new(self.highlights_query.capture_names(), theme);
166    }
167}
168
169#[cfg(any(test, feature = "test-support"))]
170impl LanguageServerConfig {
171    pub async fn fake(executor: Arc<Background>) -> (Self, lsp::FakeLanguageServer) {
172        let (server, fake) = lsp::LanguageServer::fake(executor).await;
173        fake.started
174            .store(false, std::sync::atomic::Ordering::SeqCst);
175        let started = fake.started.clone();
176        (
177            Self {
178                fake_server: Some((server, started)),
179                ..Default::default()
180            },
181            fake,
182        )
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_select_language() {
192        let grammar = tree_sitter_rust::language();
193        let registry = LanguageRegistry {
194            languages: vec![
195                Arc::new(Language::new(
196                    LanguageConfig {
197                        name: "Rust".to_string(),
198                        path_suffixes: vec!["rs".to_string()],
199                        ..Default::default()
200                    },
201                    grammar,
202                )),
203                Arc::new(Language::new(
204                    LanguageConfig {
205                        name: "Make".to_string(),
206                        path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
207                        ..Default::default()
208                    },
209                    grammar,
210                )),
211            ],
212        };
213
214        // matching file extension
215        assert_eq!(
216            registry.select_language("zed/lib.rs").map(|l| l.name()),
217            Some("Rust")
218        );
219        assert_eq!(
220            registry.select_language("zed/lib.mk").map(|l| l.name()),
221            Some("Make")
222        );
223
224        // matching filename
225        assert_eq!(
226            registry.select_language("zed/Makefile").map(|l| l.name()),
227            Some("Make")
228        );
229
230        // matching suffix that is not the full file extension or filename
231        assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
232        assert_eq!(
233            registry.select_language("zed/a.cars").map(|l| l.name()),
234            None
235        );
236        assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
237    }
238}