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