Files
zed/crates/project/tests/integration/search.rs
T
Piotr OsiewiczandSmit Barmase 874e91241e Respect regex assertions more faithfully with search (#62158)
- **project_search: Add failing test for regex searches with
assertions**
- **project_search: Improve handling of regex assertions**

# Objective

Fixes #55995 by removing a faulty optimization path for regex searches.


## Solution

## Testing

We've added 3 extra tests for the changed behavior.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase

> This section is optional. If this PR does not include a visual change
or does not add a new user-facing feature, you can delete this section.

- Help others understand the result of this PR by showcasing your
awesome work!
- If this PR includes a visual change, consider adding a screenshot,
GIF, or video
- A before/after comparison is very useful for changes to existing
features!

While a showcase should aim to be brief and digestible, you can use a
toggleable section to save space on longer showcases:

<details>
  <summary>Click to view showcase</summary>

My super cool demos here

</details>

---

Release Notes:

- Improved handling of regexes in project/buffer searches

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2026-08-04 11:23:39 +00:00

227 lines
5.6 KiB
Rust

use std::io::BufReader;
use language::Buffer;
use project::search::SearchQuery;
use text::Rope;
use util::{
paths::{PathMatcher, PathStyle},
rel_path::RelPath,
};
#[test]
fn path_matcher_creation_for_valid_paths() {
for valid_path in [
"file",
"Cargo.toml",
".DS_Store",
"~/dir/another_dir/",
"./dir/file",
"dir/[a-z].txt",
] {
let path_matcher = PathMatcher::new(&[valid_path.to_owned()], PathStyle::local())
.unwrap_or_else(|e| panic!("Valid path {valid_path} should be accepted, but got: {e}"));
assert!(
path_matcher.is_match(&RelPath::new(valid_path.as_ref(), PathStyle::local()).unwrap()),
"Path matcher for valid path {valid_path} should match itself"
)
}
}
#[test]
fn path_matcher_creation_for_globs() {
for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
match PathMatcher::new(&[invalid_glob.to_owned()], PathStyle::local()) {
Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
Err(_expected) => {}
}
}
for valid_glob in [
"dir/?ile",
"dir/*.txt",
"dir/**/file",
"dir/[a-z].txt",
"{dir,file}",
] {
match PathMatcher::new(&[valid_glob.to_owned()], PathStyle::local()) {
Ok(_expected) => {}
Err(e) => panic!("Valid glob should be accepted, but got: {e}"),
}
}
}
#[test]
fn test_case_sensitive_pattern_items() {
let case_sensitive = false;
let search_query = SearchQuery::regex(
"test\\C",
false,
case_sensitive,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
assert_eq!(
search_query.case_sensitive(),
true,
"Case sensitivity should be enabled when \\C pattern item is present in the query."
);
let case_sensitive = true;
let search_query = SearchQuery::regex(
"test\\c",
true,
case_sensitive,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
assert_eq!(
search_query.case_sensitive(),
false,
"Case sensitivity should be disabled when \\c pattern item is present, even if initially set to true."
);
let case_sensitive = false;
let search_query = SearchQuery::regex(
"test\\c\\C",
false,
case_sensitive,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
assert_eq!(
search_query.case_sensitive(),
true,
"Case sensitivity should be enabled when \\C is the last pattern item, even after a \\c."
);
let case_sensitive = false;
let search_query = SearchQuery::regex(
"tests\\\\C",
false,
case_sensitive,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
assert_eq!(
search_query.case_sensitive(),
false,
"Case sensitivity should not be enabled when \\C pattern item is preceded by a backslash."
);
}
#[gpui::test]
async fn test_multiline_regex_crlf(cx: &mut gpui::TestAppContext) {
let search_query = SearchQuery::regex(
"^hello$\r?\n",
false,
false,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
let text = Rope::from("hello\r\nworld\r\nhello\r\nworld");
let snapshot = cx
.update(|app| Buffer::build_snapshot(text, None, None, None, app))
.await;
let results = search_query.search(&snapshot, None).await;
assert_eq!(results, vec![0..7, 14..21]);
}
#[gpui::test]
async fn test_multiline_regex(cx: &mut gpui::TestAppContext) {
let search_query = SearchQuery::regex(
"^hello$\n",
false,
false,
false,
false,
Default::default(),
Default::default(),
false,
None,
)
.expect("Should be able to create a regex SearchQuery");
let text = Rope::from("hello\nworld\nhello\nworld");
let snapshot = cx
.update(|app| Buffer::build_snapshot(text, None, None, None, app))
.await;
let results = search_query.search(&snapshot, None).await;
assert_eq!(results, vec![0..6, 12..18]);
}
#[gpui::test]
async fn regex_with_eol_detects_lines() {
let re = SearchQuery::regex(
"Bool$",
false,
false,
false,
false,
PathMatcher::default(),
PathMatcher::default(),
false,
None,
)
.unwrap();
let input = " Bool\nsomething else";
let result = re
.detect(BufReader::new(Box::new(input.as_bytes())))
.await
.unwrap();
assert!(result.is_some());
}
#[gpui::test]
async fn multi_line_regex_detects_matches() {
let re = SearchQuery::regex(
"Bool$\nbool",
false,
false,
false,
false,
PathMatcher::default(),
PathMatcher::default(),
false,
None,
)
.unwrap();
let input = " Bool\nbool";
let result = re
.detect(BufReader::new(Box::new(input.as_bytes())))
.await
.unwrap();
assert!(result.is_some());
}