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(
9    clippy::panic,
10    reason = "Intentional compatibility, platform, or test-only suppression."
11)]
12static AT_PATTERN_REGEX: LazyLock<Regex> =
13    LazyLock::new(|| match Regex::new(r#"@(?:\"([^\"]+)\"|'([^']+)'|([^\s"'\[\](){}<>|\\^`]+))"#) {
14        Ok(regex) => regex,
15        Err(error) => panic!("Failed to compile @ pattern regex: {error}"),
16    });
17
18/// A parsed match of an @ pattern
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct AtPatternMatch<'a> {
21    /// The full text of the match (e.g., "@file.txt")
22    pub full_match: &'a str,
23    /// The extracted path or URL part (e.g., "file.txt")
24    pub path: &'a str,
25    /// Start position in the original string
26    pub start: usize,
27    /// End position in the original string
28    pub end: usize,
29}
30
31/// Find all @ patterns in the given text
32pub fn find_at_patterns(text: &str) -> Vec<AtPatternMatch<'_>> {
33    AT_PATTERN_REGEX
34        .captures_iter(text)
35        .filter_map(|cap| {
36            let full_match = cap.get(0)?;
37            let path_part = cap.get(1).or_else(|| cap.get(2)).or_else(|| cap.get(3))?;
38
39            Some(AtPatternMatch {
40                full_match: full_match.as_str(),
41                path: path_part.as_str(),
42                start: full_match.start(),
43                end: full_match.end(),
44            })
45        })
46        .collect()
47}