121 lines
4.2 KiB
Rust
121 lines
4.2 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use std::path::Path;
|
|
|
|
#[derive(Default)]
|
|
pub struct LspGlobSet {
|
|
patterns: Vec<glob::Pattern>,
|
|
}
|
|
|
|
impl LspGlobSet {
|
|
pub fn clear(&mut self) {
|
|
self.patterns.clear();
|
|
}
|
|
|
|
/// Add a pattern to the glob set.
|
|
///
|
|
/// LSP's glob syntax supports bash-style brace expansion. For example,
|
|
/// the pattern '*.{js,ts}' would match all JavaScript or TypeScript files.
|
|
/// This is not a part of the standard libc glob syntax, and isn't supported
|
|
/// by the `glob` crate. So we pre-process the glob patterns, producing a
|
|
/// separate glob `Pattern` object for each part of a brace expansion.
|
|
pub fn add_pattern(&mut self, pattern: &str) -> Result<()> {
|
|
// Find all of the ranges of `pattern` that contain matched curly braces.
|
|
let mut expansion_ranges = Vec::new();
|
|
let mut expansion_start_ix = None;
|
|
for (ix, c) in pattern.match_indices(|c| ['{', '}'].contains(&c)) {
|
|
match c {
|
|
"{" => {
|
|
if expansion_start_ix.is_some() {
|
|
return Err(anyhow!("nested braces in glob patterns aren't supported"));
|
|
}
|
|
expansion_start_ix = Some(ix);
|
|
}
|
|
"}" => {
|
|
if let Some(start_ix) = expansion_start_ix {
|
|
expansion_ranges.push(start_ix..ix + 1);
|
|
}
|
|
expansion_start_ix = None;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Starting with a single pattern, process each brace expansion by cloning
|
|
// the pattern once per element of the expansion.
|
|
let mut unexpanded_patterns = vec![];
|
|
let mut expanded_patterns = vec![pattern.to_string()];
|
|
|
|
for outer_range in expansion_ranges.into_iter().rev() {
|
|
let inner_range = (outer_range.start + 1)..(outer_range.end - 1);
|
|
std::mem::swap(&mut unexpanded_patterns, &mut expanded_patterns);
|
|
for unexpanded_pattern in unexpanded_patterns.drain(..) {
|
|
for part in unexpanded_pattern[inner_range.clone()].split(',') {
|
|
let mut expanded_pattern = unexpanded_pattern.clone();
|
|
expanded_pattern.replace_range(outer_range.clone(), part);
|
|
expanded_patterns.push(expanded_pattern);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse the final glob patterns and add them to the set.
|
|
for pattern in expanded_patterns {
|
|
let pattern = glob::Pattern::new(&pattern)?;
|
|
self.patterns.push(pattern);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn matches(&self, path: &Path) -> bool {
|
|
self.patterns
|
|
.iter()
|
|
.any(|pattern| pattern.matches_path(path))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_glob_set() {
|
|
let mut watch = LspGlobSet::default();
|
|
watch.add_pattern("/a/**/*.rs").unwrap();
|
|
watch.add_pattern("/a/**/Cargo.toml").unwrap();
|
|
|
|
assert!(watch.matches("/a/b.rs".as_ref()));
|
|
assert!(watch.matches("/a/b/c.rs".as_ref()));
|
|
|
|
assert!(!watch.matches("/b/c.rs".as_ref()));
|
|
assert!(!watch.matches("/a/b.ts".as_ref()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_brace_expansion() {
|
|
let mut watch = LspGlobSet::default();
|
|
watch.add_pattern("/a/*.{ts,js,tsx}").unwrap();
|
|
|
|
assert!(watch.matches("/a/one.js".as_ref()));
|
|
assert!(watch.matches("/a/two.ts".as_ref()));
|
|
assert!(watch.matches("/a/three.tsx".as_ref()));
|
|
|
|
assert!(!watch.matches("/a/one.j".as_ref()));
|
|
assert!(!watch.matches("/a/two.s".as_ref()));
|
|
assert!(!watch.matches("/a/three.t".as_ref()));
|
|
assert!(!watch.matches("/a/four.t".as_ref()));
|
|
assert!(!watch.matches("/a/five.xt".as_ref()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_brace_expansion() {
|
|
let mut watch = LspGlobSet::default();
|
|
watch.add_pattern("/a/{one,two,three}.{b*c,d*e}").unwrap();
|
|
|
|
assert!(watch.matches("/a/one.bic".as_ref()));
|
|
assert!(watch.matches("/a/two.dole".as_ref()));
|
|
assert!(watch.matches("/a/three.deeee".as_ref()));
|
|
|
|
assert!(!watch.matches("/a/four.bic".as_ref()));
|
|
assert!(!watch.matches("/a/one.be".as_ref()));
|
|
}
|
|
}
|