This commit is contained in:
Dino 2025-08-26 16:02:01 -04:00 committed by GitHub
commit 5398443e94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 159 additions and 9 deletions

View file

@ -143,7 +143,7 @@ impl SearchQuery {
pub fn regex(
query: impl ToString,
whole_word: bool,
case_sensitive: bool,
mut case_sensitive: bool,
include_ignored: bool,
one_match_per_line: bool,
files_to_include: PathMatcher,
@ -153,6 +153,14 @@ impl SearchQuery {
) -> Result<Self> {
let mut query = query.to_string();
let initial_query = Arc::from(query.as_str());
if let Some((case_sensitive_from_pattern, new_query)) =
Self::case_sensitive_from_pattern(&query)
{
case_sensitive = case_sensitive_from_pattern;
query = new_query
}
if whole_word {
let mut word_query = String::new();
if let Some(first) = query.get(0..1)
@ -192,6 +200,45 @@ impl SearchQuery {
})
}
/// Extracts case sensitivity settings from pattern items in the provided
/// query and returns the same query, with the pattern items removed.
///
/// The following pattern modifiers are supported:
///
/// - `\c` (case_sensitive: false)
/// - `\C` (case_sensitive: true)
///
/// If no pattern item were found, `None` will be returned.
fn case_sensitive_from_pattern(query: &str) -> Option<(bool, String)> {
if !(query.contains("\\c") || query.contains("\\C")) {
return None;
}
let mut was_escaped = false;
let mut new_query = String::new();
let mut is_case_sensitive = None;
for c in query.chars() {
if was_escaped {
if c == 'c' {
is_case_sensitive = Some(false);
} else if c == 'C' {
is_case_sensitive = Some(true);
} else {
new_query.push('\\');
new_query.push(c);
}
was_escaped = false
} else if c == '\\' {
was_escaped = true
} else {
new_query.push(c);
}
}
is_case_sensitive.map(|c| (c, new_query))
}
pub fn from_proto(message: proto::SearchQuery) -> Result<Self> {
let files_to_include = if message.files_to_include.is_empty() {
message
@ -596,4 +643,87 @@ mod tests {
}
}
}
#[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."
);
}
}

View file

@ -810,6 +810,7 @@ impl BufferSearchBar {
});
}
/// Returns the sanitized query string with pattern items removed.
pub fn query(&self, cx: &App) -> String {
self.query_editor.read(cx).text(cx)
}
@ -909,6 +910,17 @@ impl BufferSearchBar {
}
}
pub fn disable_search_option(
&mut self,
search_option: SearchOptions,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.search_options.contains(search_option) {
self.toggle_search_option(search_option, window, cx)
}
}
pub fn set_search_options(&mut self, search_options: SearchOptions, cx: &mut Context<Self>) {
self.search_options = search_options;
self.adjust_query_regex_language(cx);
@ -1514,18 +1526,25 @@ mod tests {
cx,
)
});
let cx = cx.add_empty_window();
let editor =
cx.new_window_entity(|window, cx| Editor::for_buffer(buffer.clone(), None, window, cx));
let search_bar = cx.new_window_entity(|window, cx| {
let mut editor = None;
let window = cx.add_window(|window, cx| {
let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
"keymaps/default-macos.json",
cx,
)
.unwrap();
cx.bind_keys(default_key_bindings);
editor = Some(cx.new(|cx| Editor::for_buffer(buffer.clone(), None, window, cx)));
let mut search_bar = BufferSearchBar::new(None, window, cx);
search_bar.set_active_pane_item(Some(&editor), window, cx);
search_bar.set_active_pane_item(Some(&editor.clone().unwrap()), window, cx);
search_bar.show(window, cx);
search_bar
});
let search_bar = window.root(cx).unwrap();
(editor, search_bar, cx)
let cx = VisualTestContext::from_window(*window, cx).into_mut();
(editor.unwrap(), search_bar, cx)
}
#[gpui::test]

View file

@ -1133,13 +1133,14 @@ impl ProjectSearchView {
}
}
/// Returns the search query text without pattern items.
pub fn search_query_text(&self, cx: &App) -> String {
self.query_editor.read(cx).text(cx)
}
fn build_search_query(&mut self, cx: &mut Context<Self>) -> Option<SearchQuery> {
// Do not bail early in this function, as we want to fill out `self.panels_with_errors`.
let text = self.query_editor.read(cx).text(cx);
let text = self.search_query_text(cx);
let open_buffers = if self.included_opened_only {
Some(self.open_buffers(cx))
} else {