Enable look-around in Project Search using fancy-regex crate (#20308)

Will Bradley and Max created

Closes #13486 

Release Notes:

- Added support for look-around in project search

Co-authored-by: Max <max@zed.dev>

Change summary

Cargo.lock                   | 31 +++++++++++++++++++++++++++++--
Cargo.toml                   |  1 +
crates/project/Cargo.toml    |  1 +
crates/project/src/search.rs | 13 +++++++------
4 files changed, 38 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1588,7 +1588,7 @@ dependencies = [
  "bitflags 2.6.0",
  "cexpr",
  "clang-sys",
- "itertools 0.10.5",
+ "itertools 0.12.1",
  "lazy_static",
  "lazycell",
  "proc-macro2",
@@ -1637,6 +1637,15 @@ dependencies = [
  "bit-vec 0.7.0",
 ]
 
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec 0.8.0",
+]
+
 [[package]]
 name = "bit-vec"
 version = "0.6.3"
@@ -1649,6 +1658,12 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
 
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
 [[package]]
 name = "bit_field"
 version = "0.10.2"
@@ -4248,6 +4263,17 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "fancy-regex"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
+dependencies = [
+ "bit-set 0.8.0",
+ "regex-automata 0.4.7",
+ "regex-syntax 0.8.4",
+]
+
 [[package]]
 name = "fast-srgb8"
 version = "1.0.0"
@@ -9044,6 +9070,7 @@ dependencies = [
  "clock",
  "collections",
  "env_logger 0.11.5",
+ "fancy-regex 0.14.0",
  "fs",
  "futures 0.3.30",
  "fuzzy",
@@ -12394,7 +12421,7 @@ dependencies = [
  "anyhow",
  "base64 0.21.7",
  "bstr",
- "fancy-regex",
+ "fancy-regex 0.12.0",
  "lazy_static",
  "parking_lot",
  "rustc-hash 1.1.0",

Cargo.toml 🔗

@@ -350,6 +350,7 @@ ec4rs = "1.1"
 emojis = "0.6.1"
 env_logger = "0.11"
 exec = "0.3.1"
+fancy-regex = "0.14.0"
 fork = "0.2.0"
 futures = "0.3"
 futures-batch = "0.6.1"

crates/project/Cargo.toml 🔗

@@ -70,6 +70,7 @@ text.workspace = true
 util.workspace = true
 url.workspace = true
 which.workspace = true
+fancy-regex.workspace = true
 
 [target.'cfg(target_os = "windows")'.dependencies]
 windows.workspace = true

crates/project/src/search.rs 🔗

@@ -1,9 +1,9 @@
 use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
 use anyhow::Result;
 use client::proto;
+use fancy_regex::{Captures, Regex, RegexBuilder};
 use gpui::Model;
 use language::{Buffer, BufferSnapshot};
-use regex::{Captures, Regex, RegexBuilder};
 use smol::future::yield_now;
 use std::{
     borrow::Cow,
@@ -128,7 +128,6 @@ impl SearchQuery {
         let multiline = query.contains('\n') || query.contains("\\n") || query.contains("\\s");
         let regex = RegexBuilder::new(&query)
             .case_insensitive(!case_sensitive)
-            .multi_line(multiline)
             .build()?;
         let inner = SearchInputs {
             query: initial_query,
@@ -222,12 +221,12 @@ impl SearchQuery {
                     if let Err(err) = reader.read_to_string(&mut text) {
                         Err(err.into())
                     } else {
-                        Ok(regex.find(&text).is_some())
+                        Ok(regex.find(&text)?.is_some())
                     }
                 } else {
                     for line in reader.lines() {
                         let line = line?;
-                        if regex.find(&line).is_some() {
+                        if regex.find(&line)?.is_some() {
                             return Ok(true);
                         }
                     }
@@ -332,7 +331,9 @@ impl SearchQuery {
                             yield_now().await;
                         }
 
-                        matches.push(mat.start()..mat.end());
+                        if let Ok(mat) = mat {
+                            matches.push(mat.start()..mat.end());
+                        }
                     }
                 } else {
                     let mut line = String::new();
@@ -344,7 +345,7 @@ impl SearchQuery {
 
                         for (newline_ix, text) in chunk.split('\n').enumerate() {
                             if newline_ix > 0 {
-                                for mat in regex.find_iter(&line) {
+                                for mat in regex.find_iter(&line).flatten() {
                                     let start = line_offset + mat.start();
                                     let end = line_offset + mat.end();
                                     matches.push(start..end);