language.rs

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