language.rs

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