@@ -3,14 +3,14 @@ use anyhow::Result;
use client::proto;
use fancy_regex::{Captures, Regex, RegexBuilder};
use gpui::Model;
-use language::{Buffer, BufferSnapshot};
+use language::{Buffer, BufferSnapshot, CharKind};
use smol::future::yield_now;
use std::{
borrow::Cow,
io::{BufRead, BufReader, Read},
ops::Range,
path::Path,
- sync::{Arc, OnceLock},
+ sync::{Arc, LazyLock, OnceLock},
};
use text::Anchor;
use util::paths::PathMatcher;
@@ -76,6 +76,12 @@ pub enum SearchQuery {
},
}
+static WORD_MATCH_TEST: LazyLock<Regex> = LazyLock::new(|| {
+ RegexBuilder::new(r"\B")
+ .build()
+ .expect("Failed to create WORD_MATCH_TEST")
+});
+
impl SearchQuery {
pub fn text(
query: impl ToString,
@@ -119,9 +125,17 @@ impl SearchQuery {
let initial_query = Arc::from(query.as_str());
if whole_word {
let mut word_query = String::new();
- word_query.push_str("\\b");
+ if let Some(first) = query.get(0..1) {
+ if WORD_MATCH_TEST.is_match(first).is_ok_and(|x| !x) {
+ word_query.push_str("\\b");
+ }
+ }
word_query.push_str(&query);
- word_query.push_str("\\b");
+ if let Some(last) = query.get(query.len() - 1..) {
+ if WORD_MATCH_TEST.is_match(last).is_ok_and(|x| !x) {
+ word_query.push_str("\\b");
+ }
+ }
query = word_query
}
@@ -313,7 +327,9 @@ impl SearchQuery {
let end_kind =
classifier.kind(rope.reversed_chars_at(mat.end()).next().unwrap());
let next_kind = rope.chars_at(mat.end()).next().map(|c| classifier.kind(c));
- if Some(start_kind) == prev_kind || Some(end_kind) == next_kind {
+ if (Some(start_kind) == prev_kind && start_kind == CharKind::Word)
+ || (Some(end_kind) == next_kind && end_kind == CharKind::Word)
+ {
continue;
}
}
@@ -1866,6 +1866,86 @@ mod tests {
.unwrap();
}
+ #[gpui::test]
+ async fn test_search_query_with_match_whole_word(cx: &mut TestAppContext) {
+ init_globals(cx);
+ let buffer_text = r#"
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ edits,
+ Some(AutoindentMode::Block {
+ original_indent_columns,
+ }),
+ cx,
+ )
+ });
+
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
+ });
+ "#
+ .unindent();
+ let buffer = cx.new_model(|cx| Buffer::local(buffer_text, cx));
+ let cx = cx.add_empty_window();
+
+ let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
+
+ let search_bar = cx.new_view(|cx| {
+ let mut search_bar = BufferSearchBar::new(cx);
+ search_bar.set_active_pane_item(Some(&editor), cx);
+ search_bar.show(cx);
+ search_bar
+ });
+
+ search_bar
+ .update(cx, |search_bar, cx| {
+ search_bar.search(
+ "edit\\(",
+ Some(SearchOptions::WHOLE_WORD | SearchOptions::REGEX),
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+
+ search_bar.update(cx, |search_bar, cx| {
+ search_bar.select_all_matches(&SelectAllMatches, cx);
+ });
+ search_bar.update(cx, |_, cx| {
+ let all_selections =
+ editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+ assert_eq!(
+ all_selections.len(),
+ 2,
+ "Should select all `edit(` in the buffer, but got: {all_selections:?}"
+ );
+ });
+
+ search_bar
+ .update(cx, |search_bar, cx| {
+ search_bar.search(
+ "edit(",
+ Some(SearchOptions::WHOLE_WORD | SearchOptions::CASE_SENSITIVE),
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+
+ search_bar.update(cx, |search_bar, cx| {
+ search_bar.select_all_matches(&SelectAllMatches, cx);
+ });
+ search_bar.update(cx, |_, cx| {
+ let all_selections =
+ editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+ assert_eq!(
+ all_selections.len(),
+ 2,
+ "Should select all `edit(` in the buffer, but got: {all_selections:?}"
+ );
+ });
+ }
+
#[gpui::test]
async fn test_search_query_history(cx: &mut TestAppContext) {
init_globals(cx);