Skip to content

Commit 70f4804

Browse files
Srinivasa Reddy Sabbellasabbellasri
authored andcommitted
Add fancy regex fallback for advanced filters
1 parent 05f526c commit 70f4804

6 files changed

Lines changed: 101 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ tokio = { version = "1.52", optional = true, features = ["rt"] }
5454
toml = "1.1"
5555
unicode-width = "0.2"
5656
regex = "1.12"
57+
fancy-regex = "0.16"
5758

5859
[build-dependencies]
5960
anyhow = "1.0"

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod columns;
33
mod config;
44
mod opt;
55
mod process;
6+
mod search_regex;
67
mod style;
78
mod term_info;
89
mod util;

src/search_regex.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use anyhow::Error;
2+
3+
pub enum SearchRegex {
4+
Fast(regex::Regex),
5+
Fancy(fancy_regex::Regex),
6+
}
7+
8+
impl SearchRegex {
9+
pub fn new(pattern: &str, ignore_case: bool) -> Result<Self, Error> {
10+
if let Ok(regex) = regex::RegexBuilder::new(pattern)
11+
.case_insensitive(ignore_case)
12+
.build()
13+
{
14+
return Ok(Self::Fast(regex));
15+
}
16+
17+
let regex = fancy_regex::RegexBuilder::new(pattern)
18+
.case_insensitive(ignore_case)
19+
.build()?;
20+
Ok(Self::Fancy(regex))
21+
}
22+
23+
pub fn is_match(&self, text: &str) -> Result<bool, Error> {
24+
match self {
25+
Self::Fast(regex) => Ok(regex.is_match(text)),
26+
Self::Fancy(regex) => Ok(regex.is_match(text)?),
27+
}
28+
}
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::SearchRegex;
34+
35+
#[test]
36+
fn uses_fast_engine_for_standard_regex() {
37+
let regex = SearchRegex::new("proc.*", false).unwrap();
38+
39+
assert!(matches!(regex, SearchRegex::Fast(_)));
40+
assert!(regex.is_match("procs").unwrap());
41+
}
42+
43+
#[test]
44+
fn falls_back_to_fancy_engine_for_lookahead() {
45+
let regex = SearchRegex::new("proc(?=s)", false).unwrap();
46+
47+
assert!(matches!(regex, SearchRegex::Fancy(_)));
48+
assert!(regex.is_match("procs").unwrap());
49+
assert!(!regex.is_match("procx").unwrap());
50+
}
51+
52+
#[test]
53+
fn falls_back_to_fancy_engine_for_negative_lookahead() {
54+
let regex = SearchRegex::new("proc(?!x)", false).unwrap();
55+
56+
assert!(matches!(regex, SearchRegex::Fancy(_)));
57+
assert!(regex.is_match("procs").unwrap());
58+
assert!(!regex.is_match("procx").unwrap());
59+
}
60+
}

src/view.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::columns::*;
44
use crate::config::*;
55
use crate::opt::{ArgColorMode, ArgPagerMode};
66
use crate::process::collect_proc;
7+
use crate::search_regex::SearchRegex;
78
use crate::style::{apply_color, apply_style, color_to_column_style};
89
use crate::term_info::TermInfo;
910
use crate::util::{
@@ -13,7 +14,6 @@ use crate::util::{
1314
use anyhow::{Error, bail};
1415
#[cfg(not(target_os = "windows"))]
1516
use pager::Pager;
16-
use regex::RegexBuilder;
1717
use std::collections::HashMap;
1818
use std::time::Duration;
1919

@@ -263,9 +263,7 @@ impl View {
263263
ConfigSearchCase::Insensitive => true,
264264
ConfigSearchCase::Sensitive => false,
265265
};
266-
let regex = RegexBuilder::new(pattern)
267-
.case_insensitive(ignore_case)
268-
.build()?;
266+
let regex = SearchRegex::new(pattern, ignore_case)?;
269267
Some(regex)
270268
} else {
271269
None
@@ -316,7 +314,7 @@ impl View {
316314
} else if opt.keyword.is_empty() {
317315
true
318316
} else if let Some(regex) = &regex {
319-
View::search_regex(*pid, cols_searchable.as_slice(), regex)
317+
View::search_regex(*pid, cols_searchable.as_slice(), regex)?
320318
} else {
321319
View::search(
322320
*pid,
@@ -714,8 +712,13 @@ impl View {
714712
}
715713
}
716714

717-
fn search_regex(pid: i32, cols: &[&dyn Column], regex: &regex::Regex) -> bool {
718-
cols.iter().any(|c| regex.is_match(&c.display_json(pid)))
715+
fn search_regex(pid: i32, cols: &[&dyn Column], regex: &SearchRegex) -> Result<bool, Error> {
716+
for c in cols {
717+
if regex.is_match(&c.display_json(pid))? {
718+
return Ok(true);
719+
}
720+
}
721+
Ok(false)
719722
}
720723

721724
#[cfg(not(any(target_os = "windows", any(target_os = "linux", target_os = "android"))))]

src/watcher.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::Opt;
22
use crate::config::*;
3+
use crate::search_regex::SearchRegex;
34
use crate::term_info::TermInfo;
45
use crate::util::{get_theme, has_regex_syntax};
56
use crate::view::View;
67
use anyhow::Error;
78
use chrono::offset::Local;
89
use getch::Getch;
9-
use regex::RegexBuilder;
1010
use std::collections::HashMap;
1111
use std::sync::mpsc::{Receiver, Sender, channel};
1212
use std::thread;
@@ -221,10 +221,7 @@ impl Watcher {
221221
ConfigSearchCase::Insensitive => true,
222222
ConfigSearchCase::Sensitive => false,
223223
};
224-
match RegexBuilder::new(&candidate)
225-
.case_insensitive(ignore_case)
226-
.build()
227-
{
224+
match SearchRegex::new(&candidate, ignore_case) {
228225
Ok(_) => {
229226
opt.keyword = vec![candidate];
230227
regex_error = None;

0 commit comments

Comments
 (0)