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