language.rs

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