Skip to main content

vtcode_commons/
at_pattern.rs

1//! Utilities for parsing @ symbol patterns in user input
2
3use regex::Regex;
4use std::sync::LazyLock;
5
6/// Regex to match @ followed by a potential file path or URL
7/// Handles both quoted paths (with spaces) and unquoted paths
8#[allow(clippy::panic)]
9pub static AT_PATTERN_REGEX: LazyLock<Regex> = LazyLock::new(|| {
10    match Regex::new(r#"@(?:\"([^\"]+)\"|'([^']+)'|([^\s"'\[\](){}<>|\\^`]+))"#) {
11        Ok(regex) => regex,
12        Err(error) => panic!("Failed to compile @ pattern regex: {error}"),
13    }
14});
15
16/// A parsed match of an @ pattern
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct AtPatternMatch<'a> {
19    /// The full text of the match (e.g., "@file.txt")
20    pub full_match: &'a str,
21    /// The extracted path or URL part (e.g., "file.txt")
22    pub path: &'a str,
23    /// Start position in the original string
24    pub start: usize,
25    /// End position in the original string
26    pub end: usize,
27}
28
29/// Find all @ patterns in the given text
30pub fn find_at_patterns(text: &str) -> Vec<AtPatternMatch<'_>> {
31    AT_PATTERN_REGEX
32        .captures_iter(text)
33        .filter_map(|cap| {
34            let full_match = cap.get(0)?;
35            let path_part = cap.get(1).or_else(|| cap.get(2)).or_else(|| cap.get(3))?;
36
37            Some(AtPatternMatch {
38                full_match: full_match.as_str(),
39                path: path_part.as_str(),
40                start: full_match.start(),
41                end: full_match.end(),
42            })
43        })
44        .collect()
45}