Add fancy regex fallback for advanced filters

This commit is contained in:
Srinivasa Reddy Sabbella
2026-05-31 16:33:05 -04:00
committed by Sri Sabbella
parent fb0f1b2c0b
commit 6cecc6f09f
6 changed files with 101 additions and 12 deletions
Generated
+27
View File
@@ -185,6 +185,21 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -700,6 +715,17 @@ dependencies = [
"libc",
]
[[package]]
name = "fancy-regex"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
dependencies = [
"bit-set",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "filetime"
version = "0.2.27"
@@ -1589,6 +1615,7 @@ dependencies = [
"directories",
"dockworker",
"errno 0.3.14",
"fancy-regex",
"getch",
"libc",
"libproc",
+1
View File
@@ -54,6 +54,7 @@ tokio = { version = "1.52", optional = true, features = ["rt"] }
toml = "1.1"
unicode-width = "0.2"
regex = "1.12"
fancy-regex = "0.16"
[build-dependencies]
anyhow = "1.0"
+1
View File
@@ -3,6 +3,7 @@ mod columns;
mod config;
mod opt;
mod process;
mod search_regex;
mod style;
mod term_info;
mod util;
+60
View File
@@ -0,0 +1,60 @@
use anyhow::Error;
pub enum SearchRegex {
Fast(regex::Regex),
Fancy(fancy_regex::Regex),
}
impl SearchRegex {
pub fn new(pattern: &str, ignore_case: bool) -> Result<Self, Error> {
if let Ok(regex) = regex::RegexBuilder::new(pattern)
.case_insensitive(ignore_case)
.build()
{
return Ok(Self::Fast(regex));
}
let regex = fancy_regex::RegexBuilder::new(pattern)
.case_insensitive(ignore_case)
.build()?;
Ok(Self::Fancy(regex))
}
pub fn is_match(&self, text: &str) -> Result<bool, Error> {
match self {
Self::Fast(regex) => Ok(regex.is_match(text)),
Self::Fancy(regex) => Ok(regex.is_match(text)?),
}
}
}
#[cfg(test)]
mod tests {
use super::SearchRegex;
#[test]
fn uses_fast_engine_for_standard_regex() {
let regex = SearchRegex::new("proc.*", false).unwrap();
assert!(matches!(regex, SearchRegex::Fast(_)));
assert!(regex.is_match("procs").unwrap());
}
#[test]
fn falls_back_to_fancy_engine_for_lookahead() {
let regex = SearchRegex::new("proc(?=s)", false).unwrap();
assert!(matches!(regex, SearchRegex::Fancy(_)));
assert!(regex.is_match("procs").unwrap());
assert!(!regex.is_match("procx").unwrap());
}
#[test]
fn falls_back_to_fancy_engine_for_negative_lookahead() {
let regex = SearchRegex::new("proc(?!x)", false).unwrap();
assert!(matches!(regex, SearchRegex::Fancy(_)));
assert!(regex.is_match("procs").unwrap());
assert!(!regex.is_match("procx").unwrap());
}
}
+10 -7
View File
@@ -4,6 +4,7 @@ use crate::columns::*;
use crate::config::*;
use crate::opt::{ArgColorMode, ArgPagerMode};
use crate::process::collect_proc;
use crate::search_regex::SearchRegex;
use crate::style::{apply_color, apply_style, color_to_column_style};
use crate::term_info::TermInfo;
use crate::util::{
@@ -13,7 +14,6 @@ use crate::util::{
use anyhow::{Error, bail};
#[cfg(not(target_os = "windows"))]
use pager::Pager;
use regex::RegexBuilder;
use std::collections::HashMap;
use std::time::Duration;
@@ -263,9 +263,7 @@ impl View {
ConfigSearchCase::Insensitive => true,
ConfigSearchCase::Sensitive => false,
};
let regex = RegexBuilder::new(pattern)
.case_insensitive(ignore_case)
.build()?;
let regex = SearchRegex::new(pattern, ignore_case)?;
Some(regex)
} else {
None
@@ -316,7 +314,7 @@ impl View {
} else if opt.keyword.is_empty() {
true
} else if let Some(regex) = &regex {
View::search_regex(*pid, cols_searchable.as_slice(), regex)
View::search_regex(*pid, cols_searchable.as_slice(), regex)?
} else {
View::search(
*pid,
@@ -714,8 +712,13 @@ impl View {
}
}
fn search_regex(pid: i32, cols: &[&dyn Column], regex: &regex::Regex) -> bool {
cols.iter().any(|c| regex.is_match(&c.display_json(pid)))
fn search_regex(pid: i32, cols: &[&dyn Column], regex: &SearchRegex) -> Result<bool, Error> {
for c in cols {
if regex.is_match(&c.display_json(pid))? {
return Ok(true);
}
}
Ok(false)
}
#[cfg(not(any(target_os = "windows", any(target_os = "linux", target_os = "android"))))]
+2 -5
View File
@@ -1,12 +1,12 @@
use crate::Opt;
use crate::config::*;
use crate::search_regex::SearchRegex;
use crate::term_info::TermInfo;
use crate::util::{get_theme, has_regex_syntax};
use crate::view::View;
use anyhow::Error;
use chrono::offset::Local;
use getch::Getch;
use regex::RegexBuilder;
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
@@ -221,10 +221,7 @@ impl Watcher {
ConfigSearchCase::Insensitive => true,
ConfigSearchCase::Sensitive => false,
};
match RegexBuilder::new(&candidate)
.case_insensitive(ignore_case)
.build()
{
match SearchRegex::new(&candidate, ignore_case) {
Ok(_) => {
opt.keyword = vec![candidate];
regex_error = None;