search: Treat non-word char as whole-char when searching (#19152)
when search somethings like `clone(`, with search options `match case sensitively` and `match whole words` in zed code base, only `clone(cx)` hit match, `clone()` will not hit math. Release Notes: - Improved buffer search for queries ending with non-letter characters
This commit is contained in:
parent
3ac119ac4e
commit
cacec06db6
2 changed files with 101 additions and 5 deletions
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue