fix: fix issue with \ preceding pattern items

With the current implementation of pattern items, it's impossible to
search for strings like `\c` in buffers as, even when using `\\c`, the
`\c` suffix will be interpreted as a pattern item. This commit updates
the regex used to find the pattern items so as to ensure that the
preceding character is never a `\` and, if it is, it's considered that
the user is searching for a slash instead of trying to use a pattern
item.
This commit is contained in:
dinocosta 2025-08-18 23:37:57 +01:00
parent 09470b9d57
commit f7473c80ae
3 changed files with 28 additions and 14 deletions

2
Cargo.lock generated
View file

@ -14568,12 +14568,12 @@ dependencies = [
"client", "client",
"collections", "collections",
"editor", "editor",
"fancy-regex 0.14.0",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"language", "language",
"menu", "menu",
"project", "project",
"regex",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -42,7 +42,7 @@ util.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
regex.workspace = true fancy-regex.workspace = true
[dev-dependencies] [dev-dependencies]
client = { workspace = true, features = ["test-support"] } client = { workspace = true, features = ["test-support"] }

View file

@ -13,6 +13,7 @@ use editor::{
DisplayPoint, Editor, EditorSettings, DisplayPoint, Editor, EditorSettings,
actions::{Backtab, Tab}, actions::{Backtab, Tab},
}; };
use fancy_regex::Regex;
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _, Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
@ -24,7 +25,6 @@ use project::{
search::SearchQuery, search::SearchQuery,
search_history::{SearchHistory, SearchHistoryCursor}, search_history::{SearchHistory, SearchHistoryCursor},
}; };
use regex::Regex;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
@ -54,8 +54,8 @@ const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
// TODO: Should this be updated to an HashMap so we can easily determine the // TODO: Should this be updated to an HashMap so we can easily determine the
// search option for a given pattern? // search option for a given pattern?
static PATTERN_ITEMS: [(&str, &SearchOptions, bool); 2] = [ static PATTERN_ITEMS: [(&str, &SearchOptions, bool); 2] = [
("\\c", &SearchOptions::CASE_SENSITIVE, false), ("c", &SearchOptions::CASE_SENSITIVE, false),
("\\C", &SearchOptions::CASE_SENSITIVE, true), ("C", &SearchOptions::CASE_SENSITIVE, true),
]; ];
/// Opens the buffer search interface with the specified configuration. /// Opens the buffer search interface with the specified configuration.
@ -661,13 +661,13 @@ impl BufferSearchBar {
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
let pattern_items_regex = Regex::new( let pattern_items_regex = Regex::new(&format!(
&PATTERN_ITEMS r"(?<!\\)(\\[{}])",
PATTERN_ITEMS
.iter() .iter()
.map(|(pattern, _, _)| regex::escape(pattern)) .map(|(pattern, _, _)| *pattern)
.collect::<Vec<_>>() .collect::<String>()
.join("|"), ))
)
.unwrap(); .unwrap();
Self { Self {
@ -1528,13 +1528,16 @@ impl BufferSearchBar {
let mut pattern_item_options = Vec::new(); let mut pattern_item_options = Vec::new();
let query = self.raw_query(cx); let query = self.raw_query(cx);
// TODO: Maybe avoid so many unwrap/expect calls here.
self.pattern_items_regex self.pattern_items_regex
.captures_iter(&query) .captures_iter(&query)
.map(|capture| capture.extract()) .map(|capture| capture.unwrap())
.map(|(str, [])| { .map(|capture| {
let pattern_item = capture.get(1).unwrap().as_str();
PATTERN_ITEMS PATTERN_ITEMS
.iter() .iter()
.find(|(pattern, _, _)| *pattern == str) .find(|(pattern, _, _)| pattern_item.ends_with(*pattern))
.expect("should only capture valid pattern items") .expect("should only capture valid pattern items")
}) })
.for_each(|(_, search_option, value)| { .for_each(|(_, search_option, value)| {
@ -3079,6 +3082,17 @@ mod tests {
"Should have case sensitivity enabled when all pattern items are removed and original search options are restored" "Should have case sensitivity enabled when all pattern items are removed and original search options are restored"
); );
}); });
cx.simulate_input("\\\\c");
cx.run_until_parked();
search_bar.update(cx, |search_bar, cx| {
assert_eq!(search_bar.raw_query(cx), "test\\\\c");
assert_eq!(
search_bar.search_options,
SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE,
"Should still have case sensitivity enabled when pattern item is preceded by another \\"
);
});
} }
fn update_search_settings(search_settings: SearchSettings, cx: &mut TestAppContext) { fn update_search_settings(search_settings: SearchSettings, cx: &mut TestAppContext) {