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::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::{
 20    path::{Path, PathBuf},
 21    str,
 22    sync::Arc,
 23};
 24use theme::SyntaxTheme;
 25use tree_sitter::{self, Query};
 26pub use tree_sitter::{Parser, Tree};
 27
 28lazy_static! {
 29    pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
 30        LanguageConfig {
 31            name: "Plain Text".to_string(),
 32            path_suffixes: Default::default(),
 33            brackets: Default::default(),
 34            line_comment: None,
 35            language_server: None,
 36        },
 37        None,
 38    ));
 39}
 40
 41#[derive(Default, Deserialize)]
 42pub struct LanguageConfig {
 43    pub name: String,
 44    pub path_suffixes: Vec<String>,
 45    pub brackets: Vec<BracketPair>,
 46    pub line_comment: Option<String>,
 47    pub language_server: Option<LanguageServerConfig>,
 48}
 49
 50#[derive(Default, Deserialize)]
 51pub struct LanguageServerConfig {
 52    pub binary: String,
 53    pub disk_based_diagnostic_sources: HashSet<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
 67#[async_trait]
 68pub trait DiagnosticSource: 'static + Send + Sync {
 69    fn name(&self) -> &'static str;
 70
 71    async fn diagnose(
 72        &self,
 73        path: Arc<Path>,
 74    ) -> Result<Vec<(PathBuf, Vec<DiagnosticEntry<Point>>)>>;
 75}
 76
 77pub struct Language {
 78    pub(crate) config: LanguageConfig,
 79    pub(crate) grammar: Option<Arc<Grammar>>,
 80    pub(crate) diagnostic_source: Option<Arc<dyn DiagnosticSource>>,
 81}
 82
 83pub struct Grammar {
 84    pub(crate) ts_language: tree_sitter::Language,
 85    pub(crate) highlights_query: Query,
 86    pub(crate) brackets_query: Query,
 87    pub(crate) indents_query: Query,
 88    pub(crate) highlight_map: Mutex<HighlightMap>,
 89}
 90
 91#[derive(Default)]
 92pub struct LanguageRegistry {
 93    languages: Vec<Arc<Language>>,
 94}
 95
 96impl LanguageRegistry {
 97    pub fn new() -> Self {
 98        Self::default()
 99    }
100
101    pub fn add(&mut self, language: Arc<Language>) {
102        self.languages.push(language);
103    }
104
105    pub fn set_theme(&self, theme: &SyntaxTheme) {
106        for language in &self.languages {
107            language.set_theme(theme);
108        }
109    }
110
111    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
112        self.languages
113            .iter()
114            .find(|language| language.name() == name)
115    }
116
117    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
118        let path = path.as_ref();
119        let filename = path.file_name().and_then(|name| name.to_str());
120        let extension = path.extension().and_then(|name| name.to_str());
121        let path_suffixes = [extension, filename];
122        self.languages.iter().find(|language| {
123            language
124                .config
125                .path_suffixes
126                .iter()
127                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
128        })
129    }
130}
131
132impl Language {
133    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
134        Self {
135            config,
136            grammar: ts_language.map(|ts_language| {
137                Arc::new(Grammar {
138                    brackets_query: Query::new(ts_language, "").unwrap(),
139                    highlights_query: Query::new(ts_language, "").unwrap(),
140                    indents_query: Query::new(ts_language, "").unwrap(),
141                    ts_language,
142                    highlight_map: Default::default(),
143                })
144            }),
145            diagnostic_source: None,
146        }
147    }
148
149    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
150        let grammar = self
151            .grammar
152            .as_mut()
153            .and_then(Arc::get_mut)
154            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
155        grammar.highlights_query = Query::new(grammar.ts_language, source)?;
156        Ok(self)
157    }
158
159    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
160        let grammar = self
161            .grammar
162            .as_mut()
163            .and_then(Arc::get_mut)
164            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
165        grammar.brackets_query = Query::new(grammar.ts_language, source)?;
166        Ok(self)
167    }
168
169    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
170        let grammar = self
171            .grammar
172            .as_mut()
173            .and_then(Arc::get_mut)
174            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
175        grammar.indents_query = Query::new(grammar.ts_language, source)?;
176        Ok(self)
177    }
178
179    pub fn with_diagnostic_source(mut self, source: impl DiagnosticSource) -> Self {
180        self.diagnostic_source = Some(Arc::new(source));
181        self
182    }
183
184    pub fn name(&self) -> &str {
185        self.config.name.as_str()
186    }
187
188    pub fn line_comment_prefix(&self) -> Option<&str> {
189        self.config.line_comment.as_deref()
190    }
191
192    pub fn start_server(
193        &self,
194        root_path: &Path,
195        cx: &AppContext,
196    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
197        if let Some(config) = &self.config.language_server {
198            #[cfg(any(test, feature = "test-support"))]
199            if let Some((server, started)) = &config.fake_server {
200                started.store(true, std::sync::atomic::Ordering::SeqCst);
201                return Ok(Some(server.clone()));
202            }
203
204            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
205            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
206                cx.platform()
207                    .path_for_resource(Some(&config.binary), None)?
208            } else {
209                Path::new(&config.binary).to_path_buf()
210            };
211            lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
212        } else {
213            Ok(None)
214        }
215    }
216
217    pub fn diagnostic_source(&self) -> Option<&Arc<dyn DiagnosticSource>> {
218        self.diagnostic_source.as_ref()
219    }
220
221    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
222        self.config
223            .language_server
224            .as_ref()
225            .map(|config| &config.disk_based_diagnostic_sources)
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                ..Default::default()
259            },
260            fake,
261        )
262    }
263}