bug: fix issue with parsing quoted delimiters in process search (#2069)

Noticed this when trying to parse something with `<` and `>` in the name, even when quoted. The fix is to just check for quotes when delimiting.
This commit is contained in:
Clement Tsang authored and GitHub committed 2026-05-19 09:44:02 +00:00
1 parent acb0ddfa53
commit 6daf7ebd3f
2 files changed
+67 -4

No files matched your search

+1
View File
@@ -26,6 +26,7 @@ That said, these are more guidelines rather than hard rules, though the project
- [#2035](https://github.com/ClementTsang/bottom/pull/2035): Fix panic when deleting unicode words in search.
- [#2042](https://github.com/ClementTsang/bottom/pull/2042): Address possible memory usage growth for AMD GPU stat gathering on Linux.
- [#2069](https://github.com/ClementTsang/bottom/pull/2069): Fix issue in process search with parsing quoted delimiters.
### Features
+66 -4
View File
@@ -120,16 +120,30 @@ pub(crate) fn parse_query(search_query: &str, options: &QueryOptions) -> QueryRe
}
let mut split_query = VecDeque::new();
let mut in_quotes = false;
search_query.split_whitespace().for_each(|s| {
// From https://stackoverflow.com/a/56923739 get a split but include the parentheses
let mut last = 0;
for (index, matched) in s.match_indices(|x| DELIMITER_LIST.contains(&x)) {
if last != index {
split_query.push_back(s[last..index].to_owned());
if matched == "\"" {
// Always split on quote delimiters to open/close quoted sections.
if last != index {
split_query.push_back(s[last..index].to_owned());
}
split_query.push_back(matched.to_owned());
last = index + matched.len();
in_quotes = !in_quotes;
} else if !in_quotes {
// Only split on other delimiters when outside a quoted section,
// so that special chars like `<` and `>` in `"Thread<15>"` are
// preserved as literals.
if last != index {
split_query.push_back(s[last..index].to_owned());
}
split_query.push_back(matched.to_owned());
last = index + matched.len();
}
split_query.push_back(matched.to_owned());
last = index + matched.len();
}
if last < s.len() {
split_query.push_back(s[last..].to_owned());
@@ -1098,6 +1112,54 @@ mod tests {
assert!(!query.check(&without, false));
}
/// Quoted angle brackets should be treated as literal characters in name matches.
#[test]
fn quoted_angle_brackets_match_literal() {
let query = parse_query_no_options("\"Thread<15>\"").unwrap();
let matching = simple_process("Thread<15>");
let non_matching = simple_process("Thread");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
/// A quoted `=` should be treated as a literal character in name matches.
#[test]
fn quoted_equals_matches_literal() {
let query = parse_query_no_options("\"a=b\"").unwrap();
let matching = simple_process("a=b");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
/// A quoted `!=` should be treated as a literal character sequence in name matches.
#[test]
fn quoted_not_equals_matches_literal() {
let query = parse_query_no_options("\"a!=b\"").unwrap();
let matching = simple_process("a!=b");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
/// Quoted parentheses should be treated as literal characters in name matches.
#[test]
fn quoted_parens_match_literal() {
let query = parse_query_no_options("\"a(b)\"").unwrap();
let matching = simple_process("a(b)");
let non_matching = simple_process("ab");
assert!(query.check(&matching, false));
assert!(!query.check(&non_matching, false));
}
/// Trailing operators with no RHS must error.
#[test]
fn not_equal_missing_value_is_rejected() {