fm/modes/menu/
regex.rs

1use std::path::Path;
2
3use anyhow::Result;
4use regex::Regex;
5
6use crate::common::filename_from_path;
7use crate::modes::Flagged;
8
9/// Flag every file matching a typed regex in current directory.
10///
11/// # Errors
12///
13/// It may fail if the `input_string` can't be parsed as a regex expression.
14/// It may also fail if a file in the directory has a filename which can't be decoded as utf-8.
15pub fn regex_flagger(input_string: &str, paths: &[&Path], flagged: &mut Flagged) -> Result<()> {
16    let Ok(regex) = CaseDependantRegex::new(input_string) else {
17        return Ok(());
18    };
19    flagged.clear();
20    for path in paths {
21        if regex.is_match(filename_from_path(path)?) {
22            flagged.push(path.to_path_buf());
23        }
24    }
25
26    Ok(())
27}
28
29/// Case dependant regular expression.
30///
31/// It holds an input string (the original regular expression) and a regular expression.
32/// If the input string contains an uppercase character, we use it as is.
33/// If not, we make the regular expression case insensitive by adding `(?i)` in front of it.
34///
35/// If the input is "Car" it will match against "Car", "Cargo" but not "car".
36/// If the input is "car" it will match against "Car", "Cargo" and "car".
37///
38/// This is inspired by ranger which take it from vim/neovim.
39#[derive(Clone)]
40pub struct CaseDependantRegex {
41    input_string: String,
42    regex: Regex,
43}
44
45impl CaseDependantRegex {
46    /// Creates a new case dependant regular expression.
47    ///
48    /// # Errors
49    ///
50    /// It may fail if the input_string can't be parsed as a regular expression.
51    pub fn new(input_string: &str) -> Result<Self> {
52        Ok(Self {
53            input_string: input_string.to_string(),
54            regex: Self::complete_regex(input_string)?,
55        })
56    }
57
58    /// True if the input string is empty.
59    pub fn is_empty(&self) -> bool {
60        self.input_string.is_empty()
61    }
62
63    /// True if the regular expression matches the haystack.
64    pub fn is_match(&self, haystack: &str) -> bool {
65        self.regex.is_match(haystack)
66    }
67
68    fn complete_regex(input_string: &str) -> Result<Regex> {
69        let re = if Self::has_uppercase(input_string) {
70            input_string
71        } else {
72            &format!("(?i){input_string}")
73        };
74        Ok(Regex::new(re)?)
75    }
76
77    fn has_uppercase(input_string: &str) -> bool {
78        input_string.chars().any(|c| c.is_uppercase())
79    }
80}
81
82impl std::fmt::Display for CaseDependantRegex {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.write_str(&self.input_string)
85    }
86}