lattice_snippet/
registry.rs1use std::collections::HashMap;
6
7use crate::token::SnippetBody;
8
9#[derive(Debug, Clone)]
13pub struct Snippet {
14 pub name: String,
15 pub prefixes: Vec<String>,
16 pub body: SnippetBody,
17 pub description: Option<String>,
18 pub scope: String,
23}
24
25#[derive(Debug, Clone)]
28pub struct SnippetMeta {
29 pub name: String,
30 pub prefix: String,
31 pub description: Option<String>,
32}
33
34#[derive(Debug, Default, Clone)]
37pub struct SnippetRegistry {
38 by_language: HashMap<String, HashMap<String, Vec<Snippet>>>,
42 all_by_name: HashMap<String, Snippet>,
45}
46
47impl SnippetRegistry {
48 pub fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn insert(&mut self, language: &str, snippet: Snippet) {
57 let by_prefix = self.by_language.entry(language.to_string()).or_default();
58 for prefix in &snippet.prefixes {
59 by_prefix
60 .entry(prefix.clone())
61 .or_default()
62 .push(snippet.clone());
63 }
64 self.all_by_name.insert(snippet.name.clone(), snippet);
65 }
66
67 pub fn lookup(&self, language: &str, prefix: &str) -> Vec<&Snippet> {
74 let mut out: Vec<&Snippet> = Vec::new();
75 if let Some(by_prefix) = self.by_language.get(language)
76 && let Some(snips) = by_prefix.get(prefix)
77 {
78 out.extend(snips.iter());
79 }
80 if let Some(by_prefix) = self.by_language.get("*")
81 && let Some(snips) = by_prefix.get(prefix)
82 {
83 out.extend(snips.iter());
84 }
85 out
86 }
87
88 pub fn matching_prefix<'a>(&'a self, language: &str, query: &str) -> Vec<&'a Snippet> {
94 let q = query.to_lowercase();
95 let mut out: Vec<&'a Snippet> = Vec::new();
96 let mut seen: std::collections::HashSet<&'a str> = std::collections::HashSet::new();
97 for source_lang in [language, "*"] {
98 let Some(by_prefix) = self.by_language.get(source_lang) else {
99 continue;
100 };
101 for (prefix, snips) in by_prefix {
102 if !prefix.to_lowercase().starts_with(&q) {
103 continue;
104 }
105 for s in snips {
106 if seen.insert(s.name.as_str()) {
107 out.push(s);
108 }
109 }
110 }
111 }
112 out
113 }
114
115 pub fn by_name(&self, name: &str) -> Option<&Snippet> {
116 self.all_by_name.get(name)
117 }
118
119 pub fn len(&self) -> usize {
120 self.all_by_name.len()
121 }
122
123 pub fn is_empty(&self) -> bool {
124 self.all_by_name.is_empty()
125 }
126
127 pub fn languages(&self) -> Vec<&str> {
129 self.by_language.keys().map(|s| s.as_str()).collect()
130 }
131
132 pub fn meta_for_language(&self, language: &str) -> Vec<SnippetMeta> {
136 let mut out: Vec<SnippetMeta> = Vec::new();
137 if let Some(by_prefix) = self.by_language.get(language) {
138 for (prefix, snips) in by_prefix {
139 for s in snips {
140 out.push(SnippetMeta {
141 name: s.name.clone(),
142 prefix: prefix.clone(),
143 description: s.description.clone(),
144 });
145 }
146 }
147 }
148 out
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::parse;
156
157 fn snip(name: &str, prefix: &str, body: &str) -> Snippet {
158 Snippet {
159 name: name.into(),
160 prefixes: vec![prefix.into()],
161 body: parse::parse(body).unwrap(),
162 description: None,
163 scope: String::new(),
164 }
165 }
166
167 #[test]
168 fn lookup_returns_matching_snippet() {
169 let mut r = SnippetRegistry::new();
170 r.insert(
171 "rust",
172 snip("for-loop", "for", "for ${1:i} in ${2:iter} {}"),
173 );
174 let hits = r.lookup("rust", "for");
175 assert_eq!(hits.len(), 1);
176 assert_eq!(hits[0].name, "for-loop");
177 }
178
179 #[test]
180 fn matching_prefix_walks_per_language_then_star() {
181 let mut r = SnippetRegistry::new();
182 r.insert("rust", snip("rust-for", "for", "for $1"));
183 r.insert("*", snip("anywhere", "fn", "fn $1"));
184 let hits = r.matching_prefix("rust", "f");
185 let names: Vec<&str> = hits.iter().map(|s| s.name.as_str()).collect();
186 assert!(names.contains(&"rust-for"));
187 assert!(names.contains(&"anywhere"));
188 }
189
190 #[test]
191 fn multi_prefix_snippets_register_under_each() {
192 let mut r = SnippetRegistry::new();
193 let mut s = snip("for-or-fr", "for", "for $1");
194 s.prefixes = vec!["for".into(), "fr".into()];
195 r.insert("rust", s);
196 assert_eq!(r.lookup("rust", "for").len(), 1);
197 assert_eq!(r.lookup("rust", "fr").len(), 1);
198 }
199
200 #[test]
201 fn matching_prefix_dedups_by_snippet_name() {
202 let mut r = SnippetRegistry::new();
203 let mut s = snip("for-or-fr", "for", "for $1");
204 s.prefixes = vec!["for".into(), "fr".into()];
205 r.insert("rust", s);
206 let hits = r.matching_prefix("rust", "f");
209 assert_eq!(hits.len(), 1);
210 }
211
212 #[test]
213 fn meta_for_language_omits_body() {
214 let mut r = SnippetRegistry::new();
215 r.insert("rust", snip("for", "for", "for $1"));
216 let meta = r.meta_for_language("rust");
217 assert_eq!(meta.len(), 1);
218 assert_eq!(meta[0].name, "for");
219 assert_eq!(meta[0].prefix, "for");
220 }
221}