1use anyhow::{anyhow, Result};
2use std::path::Path;
3
4#[derive(Default)]
5pub struct LspGlobSet {
6 patterns: Vec<glob::Pattern>,
7}
8
9impl LspGlobSet {
10 pub fn clear(&mut self) {
11 self.patterns.clear();
12 }
13
14 /// Add a pattern to the glob set.
15 ///
16 /// LSP's glob syntax supports bash-style brace expansion. For example,
17 /// the pattern '*.{js,ts}' would match all JavaScript or TypeScript files.
18 /// This is not a part of the standard libc glob syntax, and isn't supported
19 /// by the `glob` crate. So we pre-process the glob patterns, producing a
20 /// separate glob `Pattern` object for each part of a brace expansion.
21 pub fn add_pattern(&mut self, pattern: &str) -> Result<()> {
22 // Find all of the ranges of `pattern` that contain matched curly braces.
23 let mut expansion_ranges = Vec::new();
24 let mut expansion_start_ix = None;
25 for (ix, c) in pattern.match_indices(|c| ['{', '}'].contains(&c)) {
26 match c {
27 "{" => {
28 if expansion_start_ix.is_some() {
29 return Err(anyhow!("nested braces in glob patterns aren't supported"));
30 }
31 expansion_start_ix = Some(ix);
32 }
33 "}" => {
34 if let Some(start_ix) = expansion_start_ix {
35 expansion_ranges.push(start_ix..ix + 1);
36 }
37 expansion_start_ix = None;
38 }
39 _ => {}
40 }
41 }
42
43 // Starting with a single pattern, process each brace expansion by cloning
44 // the pattern once per element of the expansion.
45 let mut unexpanded_patterns = vec![];
46 let mut expanded_patterns = vec![pattern.to_string()];
47
48 for outer_range in expansion_ranges.into_iter().rev() {
49 let inner_range = (outer_range.start + 1)..(outer_range.end - 1);
50 std::mem::swap(&mut unexpanded_patterns, &mut expanded_patterns);
51 for unexpanded_pattern in unexpanded_patterns.drain(..) {
52 for part in unexpanded_pattern[inner_range.clone()].split(',') {
53 let mut expanded_pattern = unexpanded_pattern.clone();
54 expanded_pattern.replace_range(outer_range.clone(), part);
55 expanded_patterns.push(expanded_pattern);
56 }
57 }
58 }
59
60 // Parse the final glob patterns and add them to the set.
61 for pattern in expanded_patterns {
62 let pattern = glob::Pattern::new(&pattern)?;
63 self.patterns.push(pattern);
64 }
65
66 Ok(())
67 }
68
69 pub fn matches(&self, path: &Path) -> bool {
70 self.patterns
71 .iter()
72 .any(|pattern| pattern.matches_path(path))
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn test_glob_set() {
82 let mut watch = LspGlobSet::default();
83 watch.add_pattern("/a/**/*.rs").unwrap();
84 watch.add_pattern("/a/**/Cargo.toml").unwrap();
85
86 assert!(watch.matches("/a/b.rs".as_ref()));
87 assert!(watch.matches("/a/b/c.rs".as_ref()));
88
89 assert!(!watch.matches("/b/c.rs".as_ref()));
90 assert!(!watch.matches("/a/b.ts".as_ref()));
91 }
92
93 #[test]
94 fn test_brace_expansion() {
95 let mut watch = LspGlobSet::default();
96 watch.add_pattern("/a/*.{ts,js,tsx}").unwrap();
97
98 assert!(watch.matches("/a/one.js".as_ref()));
99 assert!(watch.matches("/a/two.ts".as_ref()));
100 assert!(watch.matches("/a/three.tsx".as_ref()));
101
102 assert!(!watch.matches("/a/one.j".as_ref()));
103 assert!(!watch.matches("/a/two.s".as_ref()));
104 assert!(!watch.matches("/a/three.t".as_ref()));
105 assert!(!watch.matches("/a/four.t".as_ref()));
106 assert!(!watch.matches("/a/five.xt".as_ref()));
107 }
108
109 #[test]
110 fn test_multiple_brace_expansion() {
111 let mut watch = LspGlobSet::default();
112 watch.add_pattern("/a/{one,two,three}.{b*c,d*e}").unwrap();
113
114 assert!(watch.matches("/a/one.bic".as_ref()));
115 assert!(watch.matches("/a/two.dole".as_ref()));
116 assert!(watch.matches("/a/three.deeee".as_ref()));
117
118 assert!(!watch.matches("/a/four.bic".as_ref()));
119 assert!(!watch.matches("/a/one.be".as_ref()));
120 }
121}