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    async fn diagnose(
 70        &self,
 71        path: Arc<Path>,
 72    ) -> Result<Vec<(PathBuf, Vec<DiagnosticEntry<Point>>)>>;
 73}
 74
 75pub struct Language {
 76    pub(crate) config: LanguageConfig,
 77    pub(crate) grammar: Option<Arc<Grammar>>,
 78    pub(crate) diagnostic_source: Option<Arc<dyn DiagnosticSource>>,
 79}
 80
 81pub struct Grammar {
 82    pub(crate) ts_language: tree_sitter::Language,
 83    pub(crate) highlights_query: Query,
 84    pub(crate) brackets_query: Query,
 85    pub(crate) indents_query: Query,
 86    pub(crate) highlight_map: Mutex<HighlightMap>,
 87}
 88
 89#[derive(Default)]
 90pub struct LanguageRegistry {
 91    languages: Vec<Arc<Language>>,
 92}
 93
 94impl LanguageRegistry {
 95    pub fn new() -> Self {
 96        Self::default()
 97    }
 98
 99    pub fn add(&mut self, language: Arc<Language>) {
100        self.languages.push(language);
101    }
102
103    pub fn set_theme(&self, theme: &SyntaxTheme) {
104        for language in &self.languages {
105            language.set_theme(theme);
106        }
107    }
108
109    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
110        self.languages
111            .iter()
112            .find(|language| language.name() == name)
113    }
114
115    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
116        let path = path.as_ref();
117        let filename = path.file_name().and_then(|name| name.to_str());
118        let extension = path.extension().and_then(|name| name.to_str());
119        let path_suffixes = [extension, filename];
120        self.languages.iter().find(|language| {
121            language
122                .config
123                .path_suffixes
124                .iter()
125                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
126        })
127    }
128}
129
130impl Language {
131    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
132        Self {
133            config,
134            grammar: ts_language.map(|ts_language| {
135                Arc::new(Grammar {
136                    brackets_query: Query::new(ts_language, "").unwrap(),
137                    highlights_query: Query::new(ts_language, "").unwrap(),
138                    indents_query: Query::new(ts_language, "").unwrap(),
139                    ts_language,
140                    highlight_map: Default::default(),
141                })
142            }),
143            diagnostic_source: 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_diagnostic_source(mut self, source: impl DiagnosticSource) -> Self {
178        self.diagnostic_source = Some(Arc::new(source));
179        self
180    }
181
182    pub fn name(&self) -> &str {
183        self.config.name.as_str()
184    }
185
186    pub fn line_comment_prefix(&self) -> Option<&str> {
187        self.config.line_comment.as_deref()
188    }
189
190    pub fn start_server(
191        &self,
192        root_path: &Path,
193        cx: &AppContext,
194    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
195        if let Some(config) = &self.config.language_server {
196            #[cfg(any(test, feature = "test-support"))]
197            if let Some((server, started)) = &config.fake_server {
198                started.store(true, std::sync::atomic::Ordering::SeqCst);
199                return Ok(Some(server.clone()));
200            }
201
202            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
203            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
204                cx.platform()
205                    .path_for_resource(Some(&config.binary), None)?
206            } else {
207                Path::new(&config.binary).to_path_buf()
208            };
209            lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
210        } else {
211            Ok(None)
212        }
213    }
214
215    pub fn diagnostic_source(&self) -> Option<&Arc<dyn DiagnosticSource>> {
216        self.diagnostic_source.as_ref()
217    }
218
219    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
220        self.config
221            .language_server
222            .as_ref()
223            .map(|config| &config.disk_based_diagnostic_sources)
224    }
225
226    pub fn brackets(&self) -> &[BracketPair] {
227        &self.config.brackets
228    }
229
230    pub fn set_theme(&self, theme: &SyntaxTheme) {
231        if let Some(grammar) = self.grammar.as_ref() {
232            *grammar.highlight_map.lock() =
233                HighlightMap::new(grammar.highlights_query.capture_names(), theme);
234        }
235    }
236}
237
238impl Grammar {
239    pub fn highlight_map(&self) -> HighlightMap {
240        self.highlight_map.lock().clone()
241    }
242}
243
244#[cfg(any(test, feature = "test-support"))]
245impl LanguageServerConfig {
246    pub async fn fake(
247        executor: Arc<gpui::executor::Background>,
248    ) -> (Self, lsp::FakeLanguageServer) {
249        let (server, fake) = lsp::LanguageServer::fake(executor).await;
250        fake.started
251            .store(false, std::sync::atomic::Ordering::SeqCst);
252        let started = fake.started.clone();
253        (
254            Self {
255                fake_server: Some((server, started)),
256                ..Default::default()
257            },
258            fake,
259        )
260    }
261}