language.rs

  1use crate::HighlightMap;
  2use anyhow::Result;
  3use parking_lot::Mutex;
  4use serde::Deserialize;
  5use std::{path::Path, str, sync::Arc};
  6use theme::SyntaxTheme;
  7use tree_sitter::{Language as Grammar, Query};
  8pub use tree_sitter::{Parser, Tree};
  9
 10#[derive(Default, Deserialize)]
 11pub struct LanguageConfig {
 12    pub name: String,
 13    pub path_suffixes: Vec<String>,
 14    pub brackets: Vec<BracketPair>,
 15}
 16
 17#[derive(Clone, Debug, Deserialize)]
 18pub struct BracketPair {
 19    pub start: String,
 20    pub end: String,
 21    pub close: bool,
 22    pub newline: bool,
 23}
 24
 25pub struct Language {
 26    pub(crate) config: LanguageConfig,
 27    pub(crate) grammar: Grammar,
 28    pub(crate) highlights_query: Query,
 29    pub(crate) brackets_query: Query,
 30    pub(crate) indents_query: Query,
 31    pub(crate) highlight_map: Mutex<HighlightMap>,
 32}
 33
 34#[derive(Default)]
 35pub struct LanguageRegistry {
 36    languages: Vec<Arc<Language>>,
 37}
 38
 39impl LanguageRegistry {
 40    pub fn new() -> Self {
 41        Self::default()
 42    }
 43
 44    pub fn add(&mut self, language: Arc<Language>) {
 45        self.languages.push(language);
 46    }
 47
 48    pub fn set_theme(&self, theme: &SyntaxTheme) {
 49        for language in &self.languages {
 50            language.set_theme(theme);
 51        }
 52    }
 53
 54    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
 55        let path = path.as_ref();
 56        let filename = path.file_name().and_then(|name| name.to_str());
 57        let extension = path.extension().and_then(|name| name.to_str());
 58        let path_suffixes = [extension, filename];
 59        self.languages.iter().find(|language| {
 60            language
 61                .config
 62                .path_suffixes
 63                .iter()
 64                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
 65        })
 66    }
 67}
 68
 69impl Language {
 70    pub fn new(config: LanguageConfig, grammar: Grammar) -> Self {
 71        Self {
 72            config,
 73            brackets_query: Query::new(grammar, "").unwrap(),
 74            highlights_query: Query::new(grammar, "").unwrap(),
 75            indents_query: Query::new(grammar, "").unwrap(),
 76            grammar,
 77            highlight_map: Default::default(),
 78        }
 79    }
 80
 81    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
 82        self.highlights_query = Query::new(self.grammar, source)?;
 83        Ok(self)
 84    }
 85
 86    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
 87        self.brackets_query = Query::new(self.grammar, source)?;
 88        Ok(self)
 89    }
 90
 91    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
 92        self.indents_query = Query::new(self.grammar, source)?;
 93        Ok(self)
 94    }
 95
 96    pub fn name(&self) -> &str {
 97        self.config.name.as_str()
 98    }
 99
100    pub fn brackets(&self) -> &[BracketPair] {
101        &self.config.brackets
102    }
103
104    pub fn highlight_map(&self) -> HighlightMap {
105        self.highlight_map.lock().clone()
106    }
107
108    pub fn set_theme(&self, theme: &SyntaxTheme) {
109        *self.highlight_map.lock() =
110            HighlightMap::new(self.highlights_query.capture_names(), theme);
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_select_language() {
120        let grammar = tree_sitter_rust::language();
121        let registry = LanguageRegistry {
122            languages: vec![
123                Arc::new(Language::new(
124                    LanguageConfig {
125                        name: "Rust".to_string(),
126                        path_suffixes: vec!["rs".to_string()],
127                        ..Default::default()
128                    },
129                    grammar,
130                )),
131                Arc::new(Language::new(
132                    LanguageConfig {
133                        name: "Make".to_string(),
134                        path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
135                        ..Default::default()
136                    },
137                    grammar,
138                )),
139            ],
140        };
141
142        // matching file extension
143        assert_eq!(
144            registry.select_language("zed/lib.rs").map(|l| l.name()),
145            Some("Rust")
146        );
147        assert_eq!(
148            registry.select_language("zed/lib.mk").map(|l| l.name()),
149            Some("Make")
150        );
151
152        // matching filename
153        assert_eq!(
154            registry.select_language("zed/Makefile").map(|l| l.name()),
155            Some("Make")
156        );
157
158        // matching suffix that is not the full file extension or filename
159        assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
160        assert_eq!(
161            registry.select_language("zed/a.cars").map(|l| l.name()),
162            None
163        );
164        assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
165    }
166}