language.rs

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