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}