language.rs

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