1use std::borrow::Borrow;
18use std::collections::{BTreeMap, HashMap};
19use std::path::Path;
20use std::sync::Arc;
21
22use crate::config::Table;
23
24#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct LanguageId(Arc<String>);
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct LanguageDefinition {
33 pub name: LanguageId,
34 pub extensions: Vec<String>,
35 pub first_line_match: Option<String>,
36 pub scope: String,
37 #[serde(skip)]
38 pub default_config: Option<Table>,
39}
40
41#[derive(Debug, Default)]
43pub struct Languages {
44 named: BTreeMap<LanguageId, Arc<LanguageDefinition>>,
46 extensions: HashMap<String, Arc<LanguageDefinition>>,
47}
48
49impl Languages {
50 pub fn new(language_defs: &[LanguageDefinition]) -> Self {
51 let mut named = BTreeMap::new();
52 let mut extensions = HashMap::new();
53 for lang in language_defs.iter() {
54 let lang_arc = Arc::new(lang.clone());
55 named.insert(lang.name.clone(), lang_arc.clone());
56 for ext in &lang.extensions {
57 extensions.insert(ext.clone(), lang_arc.clone());
58 }
59 }
60 Languages { named, extensions }
61 }
62
63 pub fn language_for_path(&self, path: &Path) -> Option<Arc<LanguageDefinition>> {
64 path.extension()
65 .or_else(|| path.file_name())
66 .and_then(|ext| self.extensions.get(ext.to_str().unwrap_or_default()))
67 .map(Arc::clone)
68 }
69
70 pub fn language_for_name<S>(&self, name: S) -> Option<Arc<LanguageDefinition>>
71 where
72 S: AsRef<str>,
73 {
74 self.named.get(name.as_ref()).map(Arc::clone)
75 }
76
77 pub fn difference(&self, other: &Languages) -> Vec<Arc<LanguageDefinition>> {
80 self.named
81 .iter()
82 .filter(|(k, _)| !other.named.contains_key(*k))
83 .map(|(_, v)| v.clone())
84 .collect()
85 }
86
87 pub fn iter(&self) -> impl Iterator<Item = &Arc<LanguageDefinition>> {
88 self.named.values()
89 }
90}
91
92impl AsRef<str> for LanguageId {
93 fn as_ref(&self) -> &str {
94 self.0.as_ref()
95 }
96}
97
98impl Borrow<str> for LanguageId {
100 fn borrow(&self) -> &str {
101 &self.0.as_ref()
102 }
103}
104
105impl<'a> From<&'a str> for LanguageId {
106 fn from(src: &'a str) -> LanguageId {
107 LanguageId(Arc::new(src.into()))
108 }
109}
110
111#[cfg(test)]
113impl LanguageDefinition {
114 pub(crate) fn simple(name: &str, exts: &[&str], scope: &str, config: Option<Table>) -> Self {
115 LanguageDefinition {
116 name: name.into(),
117 extensions: exts.iter().map(|s| (*s).into()).collect(),
118 first_line_match: None,
119 scope: scope.into(),
120 default_config: config,
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 pub fn language_for_path() {
131 let ld_rust = LanguageDefinition {
132 name: LanguageId::from("Rust"),
133 extensions: vec![String::from("rs")],
134 scope: String::from("source.rust"),
135 first_line_match: None,
136 default_config: None,
137 };
138 let ld_commit_msg = LanguageDefinition {
139 name: LanguageId::from("Git Commit"),
140 extensions: vec![
141 String::from("COMMIT_EDITMSG"),
142 String::from("MERGE_MSG"),
143 String::from("TAG_EDITMSG"),
144 ],
145 scope: String::from("text.git.commit"),
146 first_line_match: None,
147 default_config: None,
148 };
149 let languages = Languages::new(&[ld_rust.clone(), ld_commit_msg.clone()]);
150
151 assert_eq!(
152 ld_rust.name,
153 languages.language_for_path(Path::new("/path/test.rs")).unwrap().name
154 );
155 assert_eq!(
156 ld_commit_msg.name,
157 languages.language_for_path(Path::new("/path/COMMIT_EDITMSG")).unwrap().name
158 );
159 assert_eq!(
160 ld_commit_msg.name,
161 languages.language_for_path(Path::new("/path/MERGE_MSG")).unwrap().name
162 );
163 assert_eq!(
164 ld_commit_msg.name,
165 languages.language_for_path(Path::new("/path/TAG_EDITMSG")).unwrap().name
166 );
167 }
168}