Skip to content

Commit 6fc5e2d

Browse files
committed
增加类似Vs code的搜索统计功能。
1 parent c96ab2a commit 6fc5e2d

3 files changed

Lines changed: 86 additions & 42 deletions

File tree

crates/edit/src/bin/edit/draw_editor.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
7777
{
7878
{
7979
ctx.table_next_row();
80-
ctx.label("label", loc(LocId::SearchNeedleLabel));
80+
81+
let label_text = if let Some((idx, total)) = state.search_count {
82+
format!("{} ({}/{})", loc(LocId::SearchNeedleLabel), idx, total)
83+
} else {
84+
loc(LocId::SearchNeedleLabel).to_string()
85+
};
86+
ctx.label("label", &label_text);
8187

8288
if ctx.editline("needle", &mut state.search_needle) {
8389
action = Some(SearchAction::Search);
@@ -174,7 +180,7 @@ pub fn search_execute(ctx: &mut Context, state: &mut State, action: SearchAction
174180
return;
175181
};
176182

177-
state.search_success = match action {
183+
let result = match action {
178184
SearchAction::Search => {
179185
doc.buffer.borrow_mut().find_and_select(&state.search_needle, state.search_options)
180186
}
@@ -188,8 +194,18 @@ pub fn search_execute(ctx: &mut Context, state: &mut State, action: SearchAction
188194
state.search_options,
189195
state.search_replacement.as_bytes(),
190196
),
197+
};
198+
199+
match result {
200+
Ok(count) => {
201+
state.search_success = true;
202+
state.search_count = count;
203+
}
204+
Err(_) => {
205+
state.search_success = false;
206+
state.search_count = None;
207+
}
191208
}
192-
.is_ok();
193209

194210
ctx.needs_rerender();
195211
}

crates/edit/src/bin/edit/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ pub struct State {
151151
pub search_replacement: String,
152152
pub search_options: buffer::SearchOptions,
153153
pub search_success: bool,
154+
// (current_index, total_count)
155+
pub search_count: Option<(usize, usize)>,
154156

155157
pub wants_encoding_picker: bool,
156158
pub wants_encoding_change: StateEncodingChange,
@@ -199,6 +201,7 @@ impl State {
199201
search_replacement: Default::default(),
200202
search_options: Default::default(),
201203
search_success: true,
204+
search_count: None,
202205

203206
wants_encoding_picker: false,
204207
encoding_picker_needle: Default::default(),

crates/edit/src/buffer/mod.rs

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ struct ActiveSearch {
125125
/// created. When the user manually selects text, we need to
126126
/// refresh the [`ActiveSearch::pattern`] with it.
127127
selection_generation: u32,
128-
/// Stores the text buffer offset in between searches.
129-
next_search_offset: usize,
130-
/// If we know there were no hits, we can skip searching.
131-
no_matches: bool,
128+
129+
/// All found matches in the buffer.
130+
matches: Vec<Range<usize>>,
131+
/// The index of the currently selected match in `matches`.
132+
current_match_index: Option<usize>,
132133
}
133134

134135
/// Options for a search operation.
@@ -1080,7 +1081,7 @@ impl TextBuffer {
10801081
}
10811082

10821083
/// Find the next occurrence of the given `pattern` and select it.
1083-
pub fn find_and_select(&mut self, pattern: &str, options: SearchOptions) -> apperr::Result<()> {
1084+
pub fn find_and_select(&mut self, pattern: &str, options: SearchOptions) -> apperr::Result<Option<(usize, usize)>> {
10841085
if let Some(search) = &mut self.search {
10851086
let search = search.get_mut();
10861087
// When the search input changes we must reset the search.
@@ -1097,7 +1098,7 @@ impl TextBuffer {
10971098
}
10981099

10991100
if pattern.is_empty() {
1100-
return Ok(());
1101+
return Ok(None);
11011102
}
11021103

11031104
let search = match &self.search {
@@ -1109,18 +1110,19 @@ impl TextBuffer {
11091110
}
11101111
};
11111112

1112-
// If we previously searched through the entire document and found 0 matches,
1113-
// then we can avoid searching again.
1114-
if search.no_matches {
1115-
return Ok(());
1113+
if search.matches.is_empty() {
1114+
return Ok(Some((0, 0)));
11161115
}
11171116

11181117
// If the user moved the cursor since the last search, but the needle remained the same,
11191118
// we still need to move the start of the search to the new cursor position.
11201119
let next_search_offset = match self.selection {
11211120
Some(TextBufferSelection { beg, end }) => {
11221121
if self.selection_generation == search.selection_generation {
1123-
search.next_search_offset
1122+
// search.next_search_offset was removed.
1123+
// If we have a valid selection from previous search, start after it.
1124+
let p = beg.max(end);
1125+
self.cursor_move_to_logical_internal(self.cursor, p).offset
11241126
} else {
11251127
self.cursor_move_to_logical_internal(self.cursor, beg.min(end)).offset
11261128
}
@@ -1129,7 +1131,12 @@ impl TextBuffer {
11291131
};
11301132

11311133
self.find_select_next(search, next_search_offset, true);
1132-
Ok(())
1134+
1135+
if let Some(idx) = search.current_match_index {
1136+
Ok(Some((idx + 1, search.matches.len())))
1137+
} else {
1138+
Ok(Some((0, search.matches.len())))
1139+
}
11331140
}
11341141

11351142
/// Find the next occurrence of the given `pattern` and replace it with `replacement`.
@@ -1138,7 +1145,7 @@ impl TextBuffer {
11381145
pattern: &str,
11391146
options: SearchOptions,
11401147
replacement: &[u8],
1141-
) -> apperr::Result<()> {
1148+
) -> apperr::Result<Option<(usize, usize)>> {
11421149
// Editors traditionally replace the previous search hit, not the next possible one.
11431150
if let (Some(search), Some(..)) = (&self.search, &self.selection) {
11441151
let search = unsafe { &mut *search.get() };
@@ -1161,7 +1168,7 @@ impl TextBuffer {
11611168
pattern: &str,
11621169
options: SearchOptions,
11631170
replacement: &[u8],
1164-
) -> apperr::Result<()> {
1171+
) -> apperr::Result<Option<(usize, usize)>> {
11651172
let scratch = scratch_arena(None);
11661173
let mut search = self.find_construct_search(pattern, options)?;
11671174
let mut offset = 0;
@@ -1179,7 +1186,7 @@ impl TextBuffer {
11791186
offset = self.cursor.offset;
11801187
}
11811188

1182-
Ok(())
1189+
Ok(None)
11831190
}
11841191

11851192
fn find_construct_search(
@@ -1228,7 +1235,13 @@ impl TextBuffer {
12281235
// or otherwise to the current cursor position.
12291236

12301237
let text = unsafe { icu::Text::new(self)? };
1231-
let regex = unsafe { icu::Regex::new(&sanitized_pattern, flags, &text)? };
1238+
let mut regex = unsafe { icu::Regex::new(&sanitized_pattern, flags, &text)? };
1239+
1240+
let mut matches = Vec::new();
1241+
regex.reset(0);
1242+
while let Some(range) = regex.next() {
1243+
matches.push(range);
1244+
}
12321245

12331246
Ok(ActiveSearch {
12341247
pattern: pattern.to_string(),
@@ -1237,50 +1250,62 @@ impl TextBuffer {
12371250
regex,
12381251
buffer_generation: self.buffer.generation(),
12391252
selection_generation: 0,
1240-
next_search_offset: 0,
1241-
no_matches: false,
1253+
matches,
1254+
current_match_index: None,
12421255
})
12431256
}
12441257

12451258
fn find_select_next(&mut self, search: &mut ActiveSearch, offset: usize, wrap: bool) {
12461259
if search.buffer_generation != self.buffer.generation() {
12471260
unsafe { search.regex.set_text(&mut search.text, offset) };
12481261
search.buffer_generation = self.buffer.generation();
1249-
search.next_search_offset = offset;
1250-
} else if search.next_search_offset != offset {
1251-
search.next_search_offset = offset;
1252-
search.regex.reset(offset);
1262+
1263+
search.matches.clear();
1264+
search.regex.reset(0);
1265+
while let Some(range) = search.regex.next() {
1266+
search.matches.push(range);
1267+
}
1268+
search.current_match_index = None;
1269+
}
1270+
1271+
if search.matches.is_empty() {
1272+
search.current_match_index = None;
1273+
self.set_selection(None);
1274+
return;
12531275
}
12541276

1255-
let mut hit = search.regex.next();
1256-
1257-
// If we hit the end of the buffer, and we know that there's something to find,
1258-
// start the search again from the beginning (= wrap around).
1259-
if wrap && hit.is_none() && search.next_search_offset != 0 {
1260-
search.next_search_offset = 0;
1261-
search.regex.reset(0);
1262-
hit = search.regex.next();
1277+
// Find match >= offset
1278+
let idx_res = search.matches.binary_search_by_key(&offset, |r| r.start);
1279+
let start_idx = match idx_res {
1280+
Ok(i) => i,
1281+
Err(i) => i,
1282+
};
1283+
1284+
let mut next_idx = None;
1285+
if start_idx < search.matches.len() {
1286+
next_idx = Some(start_idx);
1287+
} else if wrap {
1288+
next_idx = Some(0);
12631289
}
12641290

1265-
search.selection_generation = if let Some(range) = hit {
1266-
// Now the search offset is no more at the start of the buffer.
1267-
search.next_search_offset = range.end;
1291+
if let Some(idx) = next_idx {
1292+
search.current_match_index = Some(idx);
1293+
let range = search.matches[idx].clone();
12681294

12691295
let beg = self.cursor_move_to_offset_internal(self.cursor, range.start);
12701296
let end = self.cursor_move_to_offset_internal(beg, range.end);
12711297

12721298
unsafe { self.set_cursor(end) };
12731299
self.make_cursor_visible();
12741300

1275-
self.set_selection(Some(TextBufferSelection {
1301+
search.selection_generation = self.set_selection(Some(TextBufferSelection {
12761302
beg: beg.logical_pos,
12771303
end: end.logical_pos,
1278-
}))
1304+
}));
12791305
} else {
1280-
// Avoid searching through the entire document again if we know there's nothing to find.
1281-
search.no_matches = true;
1282-
self.set_selection(None)
1283-
};
1306+
search.current_match_index = None;
1307+
self.set_selection(None);
1308+
}
12841309
}
12851310

12861311
fn find_parse_replacement<'a>(

0 commit comments

Comments
 (0)