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