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}