lsp_glob_set.rs

  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}