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}