Skip to content

Commit 0f1dfb2

Browse files
authored
Merge pull request #913 from sabbellasri/regex-fancy-fallback
Add fancy regex fallback for advanced filters
2 parents a34c452 + 5223b95 commit 0f1dfb2

6 files changed

Lines changed: 103 additions & 16 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: 12 additions & 11 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

@@ -52,10 +52,8 @@ impl View {
5252

5353
// Adding the sort column to inserts if not already present
5454
match (&opt.sorta, &opt.sortd) {
55-
(_, Some(col)) | (Some(col), _) => {
56-
if !opt.insert.contains(col) {
57-
opt.insert.push(col.clone());
58-
}
55+
(_, Some(col)) | (Some(col), _) if !opt.insert.contains(col) => {
56+
opt.insert.push(col.clone());
5957
}
6058
_ => {}
6159
}
@@ -263,9 +261,7 @@ impl View {
263261
ConfigSearchCase::Insensitive => true,
264262
ConfigSearchCase::Sensitive => false,
265263
};
266-
let regex = RegexBuilder::new(pattern)
267-
.case_insensitive(ignore_case)
268-
.build()?;
264+
let regex = SearchRegex::new(pattern, ignore_case)?;
269265
Some(regex)
270266
} else {
271267
None
@@ -316,7 +312,7 @@ impl View {
316312
} else if opt.keyword.is_empty() {
317313
true
318314
} else if let Some(regex) = &regex {
319-
View::search_regex(*pid, cols_searchable.as_slice(), regex)
315+
View::search_regex(*pid, cols_searchable.as_slice(), regex)?
320316
} else {
321317
View::search(
322318
*pid,
@@ -714,8 +710,13 @@ impl View {
714710
}
715711
}
716712

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)))
713+
fn search_regex(pid: i32, cols: &[&dyn Column], regex: &SearchRegex) -> Result<bool, Error> {
714+
for c in cols {
715+
if regex.is_match(&c.display_json(pid))? {
716+
return Ok(true);
717+
}
718+
}
719+
Ok(false)
719720
}
720721

721722
#[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)