fix: highlight all search matches before word navigation
This commit is contained in:
+65
-28
@@ -6490,6 +6490,18 @@ impl Editor {
|
||||
pub fn search_match(&self) -> Option<SearchMatch> {
|
||||
self.active.view.search_match
|
||||
}
|
||||
pub fn search_matches_on_row(&self, row: usize) -> impl Iterator<Item = SearchMatch> + '_ {
|
||||
let query = self.active.view.search_query.as_str();
|
||||
let line = &self.active.lines[row];
|
||||
let columns = grapheme_count(query);
|
||||
search_match_bytes(line, query, self.active.view.search_whole_word).map(move |byte| {
|
||||
let column = column_after_byte(line, byte);
|
||||
SearchMatch {
|
||||
start: Cursor { row, column },
|
||||
end_column: column + columns,
|
||||
}
|
||||
})
|
||||
}
|
||||
/// Whole-row shape belongs to the active buffer, not the Visual binding context.
|
||||
pub fn visual_linewise(&self) -> bool {
|
||||
self.active.view.mode == Mode::Visual && self.active.view.linewise
|
||||
@@ -8412,7 +8424,7 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_word_search(&mut self) -> Option<Cursor> {
|
||||
fn set_word_search(&mut self) -> Option<(Cursor, bool)> {
|
||||
let cursor = self.cursor();
|
||||
let line = &self.active.lines[cursor.row];
|
||||
let origin = grapheme_byte(line, cursor.column);
|
||||
@@ -8438,25 +8450,41 @@ impl Editor {
|
||||
let end = tokens()
|
||||
.find(|(position, next)| *position > byte && *next != kind)
|
||||
.map_or(line.len(), |(position, _)| position);
|
||||
self.active.view.search_query = line[start..end].into();
|
||||
self.active.view.search_whole_word = kind == WordKind::Keyword;
|
||||
Some(Cursor {
|
||||
row: cursor.row,
|
||||
column: column_after_byte(line, start),
|
||||
})
|
||||
let query = &line[start..end];
|
||||
let whole_word = kind == WordKind::Keyword;
|
||||
let newly_active = self.active.view.search_query != query
|
||||
|| self.active.view.search_whole_word != whole_word;
|
||||
if newly_active {
|
||||
self.active.view.search_query = query.into();
|
||||
self.active.view.search_whole_word = whole_word;
|
||||
}
|
||||
Some((
|
||||
Cursor {
|
||||
row: cursor.row,
|
||||
column: column_after_byte(line, start),
|
||||
},
|
||||
newly_active,
|
||||
))
|
||||
}
|
||||
|
||||
fn seek_search(&mut self, action: Action, count: usize) {
|
||||
self.reveal_caret();
|
||||
let source = self.jump_location();
|
||||
let primary_origin = if action == Action::SearchWord {
|
||||
Some(match self.set_word_search() {
|
||||
Some(origin) => origin,
|
||||
None => return,
|
||||
})
|
||||
let Some((origin, newly_active)) = self.set_word_search() else {
|
||||
return;
|
||||
};
|
||||
if newly_active {
|
||||
self.active.view.search_match = Some(SearchMatch {
|
||||
start: origin,
|
||||
end_column: origin.column + grapheme_count(&self.active.view.search_query),
|
||||
});
|
||||
return;
|
||||
}
|
||||
Some(origin)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let source = self.jump_location();
|
||||
let forward = action != Action::SearchPrevious;
|
||||
self.active.view.search_match = None;
|
||||
if self.active.view.search_query.is_empty() {
|
||||
@@ -8470,22 +8498,7 @@ impl Editor {
|
||||
.navigable_rows(0, self.active.lines.len())
|
||||
.flat_map(|row| {
|
||||
let line = &self.active.lines[row];
|
||||
line.grapheme_indices(true).filter_map(move |(byte, _)| {
|
||||
(line[byte..].starts_with(query)
|
||||
&& GraphemeCursor::new(byte + query.len(), line.len(), true)
|
||||
.is_boundary(line, 0)
|
||||
.expect("complete line supplies grapheme context")
|
||||
&& (!whole_word
|
||||
|| (line[..byte]
|
||||
.graphemes(true)
|
||||
.next_back()
|
||||
.is_none_or(|g| word_kind(g, false) != WordKind::Keyword)
|
||||
&& line[byte + query.len()..]
|
||||
.graphemes(true)
|
||||
.next()
|
||||
.is_none_or(|g| word_kind(g, false) != WordKind::Keyword))))
|
||||
.then_some((row, byte))
|
||||
})
|
||||
search_match_bytes(line, query, whole_word).map(move |byte| (row, byte))
|
||||
})
|
||||
.collect();
|
||||
if matches.is_empty() {
|
||||
@@ -11927,6 +11940,30 @@ enum WordKind {
|
||||
EmptyLine,
|
||||
}
|
||||
|
||||
fn search_match_bytes<'a>(
|
||||
line: &'a str,
|
||||
query: &'a str,
|
||||
whole_word: bool,
|
||||
) -> impl Iterator<Item = usize> + 'a {
|
||||
let line = if query.is_empty() { "" } else { line };
|
||||
line.grapheme_indices(true).filter_map(move |(byte, _)| {
|
||||
(line[byte..].starts_with(query)
|
||||
&& GraphemeCursor::new(byte + query.len(), line.len(), true)
|
||||
.is_boundary(line, 0)
|
||||
.expect("complete line supplies grapheme context")
|
||||
&& (!whole_word
|
||||
|| (line[..byte]
|
||||
.graphemes(true)
|
||||
.next_back()
|
||||
.is_none_or(|g| word_kind(g, false) != WordKind::Keyword)
|
||||
&& line[byte + query.len()..]
|
||||
.graphemes(true)
|
||||
.next()
|
||||
.is_none_or(|g| word_kind(g, false) != WordKind::Keyword))))
|
||||
.then_some(byte)
|
||||
})
|
||||
}
|
||||
|
||||
fn word_kind(grapheme: &str, big: bool) -> WordKind {
|
||||
let ch = grapheme.chars().next().expect("graphemes are nonempty");
|
||||
if ch.is_whitespace() {
|
||||
|
||||
Reference in New Issue
Block a user