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 client::proto;
|
||||||
use fancy_regex::{Captures, Regex, RegexBuilder};
|
use fancy_regex::{Captures, Regex, RegexBuilder};
|
||||||
use gpui::Model;
|
use gpui::Model;
|
||||||
use language::{Buffer, BufferSnapshot};
|
use language::{Buffer, BufferSnapshot, CharKind};
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
io::{BufRead, BufReader, Read},
|
io::{BufRead, BufReader, Read},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, LazyLock, OnceLock},
|
||||||
};
|
};
|
||||||
use text::Anchor;
|
use text::Anchor;
|
||||||
use util::paths::PathMatcher;
|
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 {
|
impl SearchQuery {
|
||||||
pub fn text(
|
pub fn text(
|
||||||
query: impl ToString,
|
query: impl ToString,
|
||||||
|
@ -119,9 +125,17 @@ impl SearchQuery {
|
||||||
let initial_query = Arc::from(query.as_str());
|
let initial_query = Arc::from(query.as_str());
|
||||||
if whole_word {
|
if whole_word {
|
||||||
let mut word_query = String::new();
|
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(&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
|
query = word_query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +327,9 @@ impl SearchQuery {
|
||||||
let end_kind =
|
let end_kind =
|
||||||
classifier.kind(rope.reversed_chars_at(mat.end()).next().unwrap());
|
classifier.kind(rope.reversed_chars_at(mat.end()).next().unwrap());
|
||||||
let next_kind = rope.chars_at(mat.end()).next().map(|c| classifier.kind(c));
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1866,6 +1866,86 @@ mod tests {
|
||||||
.unwrap();
|
.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]
|
#[gpui::test]
|
||||||
async fn test_search_query_history(cx: &mut TestAppContext) {
|
async fn test_search_query_history(cx: &mut TestAppContext) {
|
||||||
init_globals(cx);
|
init_globals(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue