language.rs

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