language.rs

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