This reverts commit e477b545b7.
Needs 2 more fixes:
* enter does not move to the first excerpt anymore
* there could be situations when a narrowed search does not decrease the
excerpt enough to see the result onscreen
Release Notes:
- N/A
This commit is contained in:
1 parent
8839d27a65
commit
761671b2ae
13 files changed
+143
-630
No files matched your search
Generated
-1
@@ -14770,7 +14770,6 @@ dependencies = [
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"serde",
|
||||
|
||||
@@ -671,8 +671,6 @@
|
||||
"regex": false,
|
||||
// Whether to center the cursor on each search match when navigating.
|
||||
"center_on_match": false,
|
||||
// Whether to search on input in project search.
|
||||
"search_on_input": true,
|
||||
},
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
|
||||
@@ -175,8 +175,6 @@ pub struct SearchSettings {
|
||||
pub regex: bool,
|
||||
/// Whether to center the cursor on each search match when navigating.
|
||||
pub center_on_match: bool,
|
||||
/// Whether to search on input in project search.
|
||||
pub search_on_input: bool,
|
||||
}
|
||||
|
||||
impl EditorSettings {
|
||||
@@ -279,7 +277,6 @@ impl Settings for EditorSettings {
|
||||
include_ignored: search.include_ignored.unwrap(),
|
||||
regex: search.regex.unwrap(),
|
||||
center_on_match: search.center_on_match.unwrap(),
|
||||
search_on_input: search.search_on_input.unwrap(),
|
||||
},
|
||||
auto_signature_help: editor.auto_signature_help.unwrap(),
|
||||
show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(),
|
||||
|
||||
+30
-17
@@ -2037,7 +2037,7 @@ impl LhsEditor {
|
||||
base_text_buffer_snapshot = base_text_buffer.snapshot();
|
||||
remote_id = base_text_buffer.remote_id();
|
||||
}
|
||||
let excerpt_ranges = rhs_multibuffer
|
||||
let new = rhs_multibuffer
|
||||
.excerpts_for_buffer(main_buffer.remote_id(), lhs_cx)
|
||||
.into_iter()
|
||||
.map(|(_, excerpt_range)| {
|
||||
@@ -2061,26 +2061,13 @@ impl LhsEditor {
|
||||
context: point_range_to_base_text_point_range(context),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect();
|
||||
|
||||
let (new, counts) = MultiBuffer::merge_excerpt_ranges(&excerpt_ranges);
|
||||
let mut total = 0;
|
||||
let rhs_merge_groups = counts
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|count| {
|
||||
let group = rhs_excerpt_ids[total..total + count].to_vec();
|
||||
total += count;
|
||||
group
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let lhs_result = lhs_multibuffer.set_merged_excerpt_ranges_for_path(
|
||||
let lhs_result = lhs_multibuffer.update_path_excerpts(
|
||||
path_key,
|
||||
diff.read(lhs_cx).base_text_buffer().clone(),
|
||||
excerpt_ranges,
|
||||
&base_text_buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
lhs_cx,
|
||||
);
|
||||
if !lhs_result.excerpt_ids.is_empty()
|
||||
@@ -2090,7 +2077,33 @@ impl LhsEditor {
|
||||
{
|
||||
lhs_multibuffer.add_inverted_diff(diff, lhs_cx);
|
||||
}
|
||||
Some((lhs_result.excerpt_ids, rhs_merge_groups))
|
||||
|
||||
let rhs_merge_groups: Vec<Vec<ExcerptId>> = {
|
||||
let mut groups = Vec::new();
|
||||
let mut current_group = Vec::new();
|
||||
let mut last_id = None;
|
||||
|
||||
for (i, &lhs_id) in lhs_result.excerpt_ids.iter().enumerate() {
|
||||
if last_id == Some(lhs_id) {
|
||||
current_group.push(rhs_excerpt_ids[i]);
|
||||
} else {
|
||||
if !current_group.is_empty() {
|
||||
groups.push(current_group);
|
||||
}
|
||||
current_group = vec![rhs_excerpt_ids[i]];
|
||||
last_id = Some(lhs_id);
|
||||
}
|
||||
}
|
||||
if !current_group.is_empty() {
|
||||
groups.push(current_group);
|
||||
}
|
||||
groups
|
||||
};
|
||||
|
||||
let deduplicated_lhs_ids: Vec<ExcerptId> =
|
||||
lhs_result.excerpt_ids.iter().dedup().copied().collect();
|
||||
|
||||
Some((deduplicated_lhs_ids, rhs_merge_groups))
|
||||
}
|
||||
|
||||
fn sync_path_excerpts(
|
||||
|
||||
@@ -1734,7 +1734,7 @@ impl MultiBuffer {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn merge_excerpt_ranges<'a>(
|
||||
fn merge_excerpt_ranges<'a>(
|
||||
expanded_ranges: impl IntoIterator<Item = &'a ExcerptRange<Point>> + 'a,
|
||||
) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
|
||||
let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathExcerptInsertResult {
|
||||
pub inserted_ranges: Vec<Range<Anchor>>,
|
||||
pub excerpt_ids: Vec<ExcerptId>,
|
||||
pub added_new_excerpt: bool,
|
||||
}
|
||||
@@ -101,7 +100,7 @@ impl MultiBuffer {
|
||||
let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
|
||||
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
|
||||
self.set_merged_excerpt_ranges_for_path(
|
||||
path,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
@@ -109,10 +108,6 @@ impl MultiBuffer {
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
);
|
||||
(
|
||||
excerpt_insertion_result.inserted_ranges,
|
||||
excerpt_insertion_result.added_new_excerpt,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,7 +120,7 @@ impl MultiBuffer {
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
|
||||
self.set_merged_excerpt_ranges_for_path(
|
||||
path,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
@@ -133,10 +128,6 @@ impl MultiBuffer {
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
);
|
||||
(
|
||||
excerpt_insertion_result.inserted_ranges,
|
||||
excerpt_insertion_result.added_new_excerpt,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,7 +156,7 @@ impl MultiBuffer {
|
||||
|
||||
multi_buffer
|
||||
.update(&mut app, move |multi_buffer, cx| {
|
||||
let excerpt_insertion_result = multi_buffer.set_merged_excerpt_ranges_for_path(
|
||||
let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
|
||||
path_key,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
@@ -174,7 +165,7 @@ impl MultiBuffer {
|
||||
counts,
|
||||
cx,
|
||||
);
|
||||
excerpt_insertion_result.inserted_ranges
|
||||
ranges
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
@@ -273,7 +264,7 @@ impl MultiBuffer {
|
||||
}
|
||||
|
||||
/// Sets excerpts, returns `true` if at least one new excerpt was added.
|
||||
pub fn set_merged_excerpt_ranges_for_path(
|
||||
fn set_merged_excerpt_ranges_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
@@ -282,110 +273,36 @@ impl MultiBuffer {
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
counts: Vec<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> PathExcerptInsertResult {
|
||||
let (new, counts) =
|
||||
self.expand_new_ranges_to_existing(&path, buffer_snapshot, new, counts, cx);
|
||||
let (excerpt_ids, added_new_excerpt) =
|
||||
self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let insert_result = self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
|
||||
|
||||
let mut inserted_ranges = Vec::new();
|
||||
let mut result = Vec::new();
|
||||
let mut ranges = ranges.into_iter();
|
||||
for (&excerpt_id, range_count) in excerpt_ids.iter().zip(counts.into_iter()) {
|
||||
for (excerpt_id, range_count) in insert_result
|
||||
.excerpt_ids
|
||||
.into_iter()
|
||||
.zip(counts.into_iter())
|
||||
{
|
||||
for range in ranges.by_ref().take(range_count) {
|
||||
let range = Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
buffer_snapshot.anchor_before(&range.primary.start)
|
||||
..buffer_snapshot.anchor_after(&range.primary.end),
|
||||
);
|
||||
inserted_ranges.push(range)
|
||||
result.push(range)
|
||||
}
|
||||
}
|
||||
|
||||
PathExcerptInsertResult {
|
||||
inserted_ranges,
|
||||
excerpt_ids,
|
||||
added_new_excerpt,
|
||||
}
|
||||
(result, insert_result.added_new_excerpt)
|
||||
}
|
||||
|
||||
/// Expand each new merged range to encompass any overlapping existing
|
||||
/// excerpt, then re-merge. This turns "partial overlap where the union
|
||||
/// equals the existing range" into an exact match, avoiding unnecessary
|
||||
/// remove+insert churn that floods the wrap map with edits.
|
||||
fn expand_new_ranges_to_existing(
|
||||
&self,
|
||||
path: &PathKey,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
mut new: Vec<ExcerptRange<Point>>,
|
||||
counts: Vec<usize>,
|
||||
cx: &App,
|
||||
) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
|
||||
let existing = self.excerpts_by_path.get(path).cloned().unwrap_or_default();
|
||||
if existing.is_empty() || new.is_empty() {
|
||||
return (new, counts);
|
||||
}
|
||||
|
||||
let snapshot = self.snapshot(cx);
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let existing_ranges: Vec<Range<Point>> = existing
|
||||
.iter()
|
||||
.filter_map(|&id| {
|
||||
let excerpt = snapshot.excerpt(id)?;
|
||||
(excerpt.buffer_id == buffer_id)
|
||||
.then(|| excerpt.range.context.to_point(buffer_snapshot))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut changed = false;
|
||||
for new_range in &mut new {
|
||||
for existing_range in &existing_ranges {
|
||||
if new_range.context.start <= existing_range.end
|
||||
&& new_range.context.end >= existing_range.start
|
||||
{
|
||||
let expanded_start = new_range.context.start.min(existing_range.start);
|
||||
let expanded_end = new_range.context.end.max(existing_range.end);
|
||||
if expanded_start != new_range.context.start
|
||||
|| expanded_end != new_range.context.end
|
||||
{
|
||||
new_range.context.start = expanded_start;
|
||||
new_range.context.end = expanded_end;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
return (new, counts);
|
||||
}
|
||||
|
||||
let mut result_ranges: Vec<ExcerptRange<Point>> = Vec::new();
|
||||
let mut result_counts: Vec<usize> = Vec::new();
|
||||
for (range, count) in new.into_iter().zip(counts) {
|
||||
if let Some(last) = result_ranges.last_mut() {
|
||||
if last.context.end >= range.context.start
|
||||
|| last.context.end.row + 1 == range.context.start.row
|
||||
{
|
||||
last.context.end = last.context.end.max(range.context.end);
|
||||
*result_counts.last_mut().unwrap() += count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result_ranges.push(range);
|
||||
result_counts.push(count);
|
||||
}
|
||||
|
||||
(result_ranges, result_counts)
|
||||
}
|
||||
|
||||
fn update_path_excerpts(
|
||||
pub fn update_path_excerpts(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<ExcerptId>, bool) {
|
||||
) -> PathExcerptInsertResult {
|
||||
let mut insert_after = self
|
||||
.excerpts_by_path
|
||||
.range(..path.clone())
|
||||
@@ -575,6 +492,9 @@ impl MultiBuffer {
|
||||
self.excerpts_by_path.insert(path, excerpt_ids);
|
||||
}
|
||||
|
||||
(excerpt_ids, added_a_new_excerpt)
|
||||
PathExcerptInsertResult {
|
||||
excerpt_ids,
|
||||
added_new_excerpt: added_a_new_excerpt,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,6 @@ editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
lsp.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -3465,7 +3465,6 @@ mod tests {
|
||||
include_ignored: false,
|
||||
regex: false,
|
||||
center_on_match: false,
|
||||
search_on_input: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
@@ -3529,7 +3528,6 @@ mod tests {
|
||||
include_ignored: false,
|
||||
regex: false,
|
||||
center_on_match: false,
|
||||
search_on_input: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
@@ -3568,7 +3566,6 @@ mod tests {
|
||||
include_ignored: false,
|
||||
regex: false,
|
||||
center_on_match: false,
|
||||
search_on_input: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
@@ -3651,7 +3648,6 @@ mod tests {
|
||||
include_ignored: Some(search_settings.include_ignored),
|
||||
regex: Some(search_settings.regex),
|
||||
center_on_match: Some(search_settings.center_on_match),
|
||||
search_on_input: Some(search_settings.search_on_input),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, PathKey,
|
||||
SelectionEffects,
|
||||
@@ -333,7 +333,7 @@ impl ProjectSearch {
|
||||
}
|
||||
}
|
||||
|
||||
fn search(&mut self, query: SearchQuery, incremental: bool, cx: &mut Context<Self>) {
|
||||
fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) {
|
||||
let search = self.project.update(cx, |project, cx| {
|
||||
project
|
||||
.search_history_mut(SearchInputKind::Query)
|
||||
@@ -360,22 +360,18 @@ impl ProjectSearch {
|
||||
let SearchResults { rx, _task_handle } = search;
|
||||
|
||||
let mut matches = pin!(rx.ready_chunks(1024));
|
||||
|
||||
if !incremental {
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
project_search.match_ranges.clear();
|
||||
project_search
|
||||
.excerpts
|
||||
.update(cx, |excerpts, cx| excerpts.clear(cx));
|
||||
project_search.no_results = Some(true);
|
||||
project_search.limit_reached = false;
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
project_search.match_ranges.clear();
|
||||
project_search
|
||||
.excerpts
|
||||
.update(cx, |excerpts, cx| excerpts.clear(cx));
|
||||
project_search.no_results = Some(true);
|
||||
project_search.limit_reached = false;
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let mut limit_reached = false;
|
||||
let mut seen_paths = HashSet::default();
|
||||
while let Some(results) = matches.next().await {
|
||||
let (buffers_with_ranges, has_reached_limit) = cx
|
||||
.background_executor()
|
||||
@@ -396,50 +392,6 @@ impl ProjectSearch {
|
||||
})
|
||||
.await;
|
||||
limit_reached |= has_reached_limit;
|
||||
|
||||
if incremental {
|
||||
let buffers_with_ranges: Vec<_> = buffers_with_ranges
|
||||
.into_iter()
|
||||
.filter(|(_, ranges)| !ranges.is_empty())
|
||||
.collect();
|
||||
if buffers_with_ranges.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (mut chunk_ranges, chunk_paths) = project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
let mut paths = Vec::new();
|
||||
let futures = project_search.excerpts.update(cx, |excerpts, cx| {
|
||||
buffers_with_ranges
|
||||
.into_iter()
|
||||
.map(|(buffer, ranges)| {
|
||||
let path_key = PathKey::for_buffer(&buffer, cx);
|
||||
paths.push(path_key.clone());
|
||||
excerpts.set_anchored_excerpts_for_path(
|
||||
path_key,
|
||||
buffer,
|
||||
ranges,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
});
|
||||
(futures, paths)
|
||||
})
|
||||
.ok()?;
|
||||
seen_paths.extend(chunk_paths);
|
||||
while let Some(ranges) = chunk_ranges.next().await {
|
||||
smol::future::yield_now().await;
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
project_search.match_ranges.extend(ranges);
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut new_ranges = project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
project_search.excerpts.update(cx, |excerpts, cx| {
|
||||
@@ -471,43 +423,16 @@ impl ProjectSearch {
|
||||
}
|
||||
}
|
||||
|
||||
if incremental {
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
if seen_paths.is_empty() {
|
||||
project_search
|
||||
.excerpts
|
||||
.update(cx, |excerpts, cx| excerpts.clear(cx));
|
||||
} else {
|
||||
project_search.excerpts.update(cx, |excerpts, cx| {
|
||||
let stale = excerpts
|
||||
.paths()
|
||||
.filter(|path| !seen_paths.contains(*path))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for path in stale {
|
||||
excerpts.remove_excerpts_for_path(path, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
project_search.no_results = Some(project_search.match_ranges.is_empty());
|
||||
project_search.limit_reached = limit_reached;
|
||||
project_search.pending_search.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
} else {
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
if !project_search.match_ranges.is_empty() {
|
||||
project_search.no_results = Some(false);
|
||||
}
|
||||
project_search.limit_reached = limit_reached;
|
||||
project_search.pending_search.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
project_search
|
||||
.update(cx, |project_search, cx| {
|
||||
if !project_search.match_ranges.is_empty() {
|
||||
project_search.no_results = Some(false);
|
||||
}
|
||||
project_search.limit_reached = limit_reached;
|
||||
project_search.pending_search.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
None
|
||||
}));
|
||||
@@ -814,7 +739,7 @@ impl ProjectSearchView {
|
||||
&& self.query_editor.read(cx).text(cx) != *last_search_query_text
|
||||
{
|
||||
// search query has changed, restart search and bail
|
||||
self.search(false, cx);
|
||||
self.search(cx);
|
||||
return;
|
||||
}
|
||||
if self.entity.read(cx).match_ranges.is_empty() {
|
||||
@@ -842,7 +767,7 @@ impl ProjectSearchView {
|
||||
&& self.query_editor.read(cx).text(cx) != *last_search_query_text
|
||||
{
|
||||
// search query has changed, restart search and bail
|
||||
self.search(false, cx);
|
||||
self.search(cx);
|
||||
return;
|
||||
}
|
||||
if self.active_match_index.is_none() {
|
||||
@@ -941,32 +866,15 @@ impl ProjectSearchView {
|
||||
// Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
|
||||
subscriptions.push(
|
||||
cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| {
|
||||
if let EditorEvent::Edited { .. } = event {
|
||||
if EditorSettings::get_global(cx).use_smartcase_search {
|
||||
let query = this.search_query_text(cx);
|
||||
if !query.is_empty()
|
||||
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
||||
!= contains_uppercase(&query)
|
||||
{
|
||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||
}
|
||||
}
|
||||
|
||||
let search_settings = &EditorSettings::get_global(cx).search;
|
||||
if search_settings.search_on_input {
|
||||
if this.query_editor.read(cx).is_empty(cx) {
|
||||
this.entity.update(cx, |model, cx| {
|
||||
model.pending_search = None;
|
||||
model.match_ranges.clear();
|
||||
model.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx));
|
||||
model.no_results = None;
|
||||
model.limit_reached = false;
|
||||
model.last_search_query_text = None;
|
||||
cx.notify();
|
||||
});
|
||||
} else {
|
||||
this.search(true, cx);
|
||||
}
|
||||
if let EditorEvent::Edited { .. } = event
|
||||
&& EditorSettings::get_global(cx).use_smartcase_search
|
||||
{
|
||||
let query = this.search_query_text(cx);
|
||||
if !query.is_empty()
|
||||
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE)
|
||||
!= contains_uppercase(&query)
|
||||
{
|
||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||
}
|
||||
}
|
||||
cx.emit(ViewEvent::EditorEvent(event.clone()))
|
||||
@@ -1152,7 +1060,7 @@ impl ProjectSearchView {
|
||||
if let Some(new_query) = new_query {
|
||||
let entity = cx.new(|cx| {
|
||||
let mut entity = ProjectSearch::new(workspace.project().clone(), cx);
|
||||
entity.search(new_query, false, cx);
|
||||
entity.search(new_query, cx);
|
||||
entity
|
||||
});
|
||||
let weak_workspace = cx.entity().downgrade();
|
||||
@@ -1305,14 +1213,14 @@ impl ProjectSearchView {
|
||||
};
|
||||
if should_search {
|
||||
this.update(cx, |this, cx| {
|
||||
this.search(false, cx);
|
||||
this.search(cx);
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn search(&mut self, incremental: bool, cx: &mut Context<Self>) {
|
||||
fn search(&mut self, cx: &mut Context<Self>) {
|
||||
let open_buffers = if self.included_opened_only {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| self.open_buffers(cx, workspace))
|
||||
@@ -1321,8 +1229,7 @@ impl ProjectSearchView {
|
||||
None
|
||||
};
|
||||
if let Some(query) = self.build_search_query(cx, open_buffers) {
|
||||
self.entity
|
||||
.update(cx, |model, cx| model.search(query, incremental, cx));
|
||||
self.entity.update(cx, |model, cx| model.search(query, cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,15 +1491,10 @@ impl ProjectSearchView {
|
||||
}
|
||||
|
||||
fn entity_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let model = self.entity.read(cx);
|
||||
let match_ranges = model.match_ranges.clone();
|
||||
let is_incremental_pending =
|
||||
model.pending_search.is_some() && EditorSettings::get_global(cx).search.search_on_input;
|
||||
let match_ranges = self.entity.read(cx).match_ranges.clone();
|
||||
|
||||
if match_ranges.is_empty() {
|
||||
if !is_incremental_pending {
|
||||
self.active_match_index = None;
|
||||
}
|
||||
self.active_match_index = None;
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
editor.clear_background_highlights(HighlightKey::ProjectSearchView, cx);
|
||||
});
|
||||
@@ -1612,11 +1514,7 @@ impl ProjectSearchView {
|
||||
editor.scroll(Point::default(), Some(Axis::Vertical), window, cx);
|
||||
}
|
||||
});
|
||||
let should_auto_focus = !EditorSettings::get_global(cx).search.search_on_input;
|
||||
if is_new_search
|
||||
&& self.query_editor.focus_handle(cx).is_focused(window)
|
||||
&& should_auto_focus
|
||||
{
|
||||
if is_new_search && self.query_editor.focus_handle(cx).is_focused(window) {
|
||||
self.focus_results_editor(window, cx);
|
||||
}
|
||||
}
|
||||
@@ -1679,13 +1577,9 @@ impl ProjectSearchView {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(if EditorSettings::get_global(cx).search.search_on_input {
|
||||
"Start typing to search. For more options:"
|
||||
} else {
|
||||
"Hit enter to search. For more options:"
|
||||
})
|
||||
.color(Color::Muted)
|
||||
.mb_2(),
|
||||
Label::new("Hit enter to search. For more options:")
|
||||
.color(Color::Muted)
|
||||
.mb_2(),
|
||||
)
|
||||
.child(
|
||||
Button::new("filter-paths", "Include/exclude specific paths")
|
||||
@@ -2604,7 +2498,7 @@ pub fn perform_project_search(
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text(text, window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
}
|
||||
@@ -2612,9 +2506,7 @@ pub fn perform_project_search(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{self, AtomicUsize},
|
||||
@@ -2626,7 +2518,6 @@ pub mod tests {
|
||||
use editor::{DisplayPoint, display_map::DisplayRow};
|
||||
use gpui::{Action, TestAppContext, VisualTestContext, WindowHandle};
|
||||
use language::{FakeLspAdapter, rust_lang};
|
||||
use multi_buffer::Event as MultiBufferEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
@@ -2637,155 +2528,6 @@ pub mod tests {
|
||||
use util_macros::perf;
|
||||
use workspace::{DeploySearch, MultiWorkspace};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ExcerptEvent {
|
||||
Added { excerpts: usize },
|
||||
Removed { ids: usize },
|
||||
Edited,
|
||||
}
|
||||
|
||||
fn subscribe_to_excerpt_events(
|
||||
search: &Entity<ProjectSearch>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (Rc<RefCell<Vec<ExcerptEvent>>>, Subscription) {
|
||||
let events: Rc<RefCell<Vec<ExcerptEvent>>> = Rc::default();
|
||||
let excerpts = cx.update(|cx| search.read(cx).excerpts.clone());
|
||||
let subscription = cx.update({
|
||||
let events = events.clone();
|
||||
|cx| {
|
||||
cx.subscribe(
|
||||
&excerpts,
|
||||
move |_, event: &MultiBufferEvent, _| match event {
|
||||
MultiBufferEvent::ExcerptsAdded { excerpts, .. } => {
|
||||
events.borrow_mut().push(ExcerptEvent::Added {
|
||||
excerpts: excerpts.len(),
|
||||
});
|
||||
}
|
||||
MultiBufferEvent::ExcerptsRemoved { ids, .. } => {
|
||||
events
|
||||
.borrow_mut()
|
||||
.push(ExcerptEvent::Removed { ids: ids.len() });
|
||||
}
|
||||
MultiBufferEvent::Edited { .. } => {
|
||||
events.borrow_mut().push(ExcerptEvent::Edited);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
}
|
||||
});
|
||||
(events, subscription)
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
editor::init(cx);
|
||||
crate::init(cx);
|
||||
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.editor
|
||||
.search
|
||||
.get_or_insert_default()
|
||||
.search_on_input = Some(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn perform_search(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
text: impl Into<Arc<str>>,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text(text, window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
})
|
||||
.unwrap();
|
||||
// Ensure editor highlights appear after the search is done
|
||||
cx.executor().advance_clock(
|
||||
editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
|
||||
);
|
||||
cx.background_executor.run_until_parked();
|
||||
}
|
||||
|
||||
fn perform_incremental_search(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
text: impl Into<Arc<str>>,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text(text, window, cx)
|
||||
});
|
||||
search_view.search(true, cx);
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(
|
||||
editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
|
||||
);
|
||||
cx.background_executor.run_until_parked();
|
||||
}
|
||||
|
||||
fn read_match_count(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> usize {
|
||||
search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
search_view.entity.read(cx).match_ranges.len()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn read_match_texts(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
search_view
|
||||
.read_with(cx, |search_view, cx| {
|
||||
let search = search_view.entity.read(cx);
|
||||
let snapshot = search.excerpts.read(cx).snapshot(cx);
|
||||
search
|
||||
.match_ranges
|
||||
.iter()
|
||||
.map(|range| snapshot.text_for_range(range.clone()).collect::<String>())
|
||||
.collect()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn assert_all_highlights_match_query(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
query: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let match_texts = read_match_texts(search_view, cx);
|
||||
assert_eq!(
|
||||
match_texts.len(),
|
||||
read_match_count(search_view, cx),
|
||||
"match texts count should equal match_ranges count for query {query:?}"
|
||||
);
|
||||
for text in &match_texts {
|
||||
assert_eq!(
|
||||
text.to_uppercase(),
|
||||
query.to_uppercase(),
|
||||
"every highlighted range should match the query {query:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_glob_patterns() {
|
||||
assert_eq!(split_glob_patterns("a,b,c"), vec!["a", "b", "c"]);
|
||||
@@ -3331,7 +3073,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3375,7 +3117,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("TWO", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3528,7 +3270,7 @@ pub mod tests {
|
||||
.update(cx, |exclude_editor, cx| {
|
||||
exclude_editor.set_text("four.rs", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3558,7 +3300,7 @@ pub mod tests {
|
||||
.update(cx, |_, _, cx| {
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view.toggle_filters(cx);
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3685,7 +3427,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3730,7 +3472,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("TWO", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3830,7 +3572,7 @@ pub mod tests {
|
||||
search_view_2.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("FOUR", window, cx)
|
||||
});
|
||||
search_view_2.search(false, cx);
|
||||
search_view_2.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3976,7 +3718,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("const", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -4051,7 +3793,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("ONE", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -4063,7 +3805,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("TWO", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -4074,7 +3816,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("THREE", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
@@ -4231,7 +3973,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text("TWO_NEW", window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -4445,7 +4187,7 @@ pub mod tests {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text(query, window, cx)
|
||||
});
|
||||
search_view.search(false, cx);
|
||||
search_view.search(cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
@@ -5145,166 +4887,35 @@ pub mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_incremental_search_narrows_and_widens(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
|
||||
// Two matches 5 lines apart: with context_line_count=2, contexts
|
||||
// [3..7] and [8..12] are adjacent and merge into a single excerpt
|
||||
// [3..12]. Narrowing to "targeted" produces context [3..7] ⊂ [3..12]
|
||||
// — the expand_new_ranges_to_existing fix ensures zero excerpt events.
|
||||
let mut lines: Vec<String> = (0..20).map(|i| format!("line {i}: filler")).collect();
|
||||
lines[5] = "line 5: targeted item".into();
|
||||
lines[10] = "line 10: target item".into();
|
||||
let big_file = lines.join("\n");
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;\nconst ONEROUS: usize = 2;",
|
||||
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
|
||||
"three.rs": "const THREE: usize = one::ONE + two::TWO;",
|
||||
"four.rs": "const FOUR: usize = one::ONE + three::THREE;",
|
||||
"only_one.rs": "const ONLY_ONE: usize = 1;",
|
||||
"big.txt": big_file,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window =
|
||||
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
|
||||
let search_view = cx.add_window(|window, cx| {
|
||||
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
|
||||
editor::init(cx);
|
||||
crate::init(cx);
|
||||
});
|
||||
let (events, _subscription) = subscribe_to_excerpt_events(&search, cx);
|
||||
let take_excerpt_changes = || -> Vec<ExcerptEvent> {
|
||||
events
|
||||
.borrow_mut()
|
||||
.drain(..)
|
||||
.filter(|e| !matches!(e, ExcerptEvent::Edited))
|
||||
.collect()
|
||||
};
|
||||
let expected_one_matches = vec![
|
||||
"one", "ONE", "ONE", "ONE", "ONE", "one", "ONE", "one", "ONE", "one", "ONE",
|
||||
];
|
||||
}
|
||||
|
||||
// Initial non-incremental search for "ONE" — clears then inserts one excerpt per file.
|
||||
perform_search(search_view, "ONE", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
|
||||
assert_all_highlights_match_query(search_view, "ONE", cx);
|
||||
assert_eq!(
|
||||
take_excerpt_changes(),
|
||||
vec![
|
||||
ExcerptEvent::Removed { ids: 0 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
]
|
||||
fn perform_search(
|
||||
search_view: WindowHandle<ProjectSearchView>,
|
||||
text: impl Into<Arc<str>>,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.query_editor.update(cx, |query_editor, cx| {
|
||||
query_editor.set_text(text, window, cx)
|
||||
});
|
||||
search_view.search(cx);
|
||||
})
|
||||
.unwrap();
|
||||
// Ensure editor highlights appear after the search is done
|
||||
cx.executor().advance_clock(
|
||||
editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
|
||||
);
|
||||
|
||||
// Natural narrowing: typing "R" after "ONE" -> "ONER".
|
||||
// Only one.rs has ONEROUS, 4 other files removed.
|
||||
perform_incremental_search(search_view, "ONER", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), vec!["ONER"]);
|
||||
assert_all_highlights_match_query(search_view, "ONER", cx);
|
||||
assert_eq!(
|
||||
take_excerpt_changes(),
|
||||
vec![
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
// Continue typing "OUS" -> "ONEROUS". Still one.rs only, zero excerpt churn.
|
||||
perform_incremental_search(search_view, "ONEROUS", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), vec!["ONEROUS"]);
|
||||
assert_all_highlights_match_query(search_view, "ONEROUS", cx);
|
||||
assert_eq!(take_excerpt_changes(), Vec::new());
|
||||
|
||||
// Backspace to "ONER" — still one.rs only, zero events.
|
||||
perform_incremental_search(search_view, "ONER", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), vec!["ONER"]);
|
||||
assert_all_highlights_match_query(search_view, "ONER", cx);
|
||||
assert_eq!(take_excerpt_changes(), Vec::new());
|
||||
|
||||
// Backspace to "ONE" — 4 files re-added.
|
||||
perform_incremental_search(search_view, "ONE", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
|
||||
assert_all_highlights_match_query(search_view, "ONE", cx);
|
||||
assert_eq!(
|
||||
take_excerpt_changes(),
|
||||
vec![
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
// Repeat the same "ONE" query — excerpts already match, zero events emitted.
|
||||
perform_incremental_search(search_view, "ONE", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
|
||||
assert_all_highlights_match_query(search_view, "ONE", cx);
|
||||
assert_eq!(events.borrow().len(), 0);
|
||||
|
||||
// Narrow to "ONLY_ONE" — single match in only_one.rs, 4 files removed.
|
||||
perform_incremental_search(search_view, "ONLY_ONE", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), vec!["ONLY_ONE"]);
|
||||
assert_all_highlights_match_query(search_view, "ONLY_ONE", cx);
|
||||
assert_eq!(
|
||||
take_excerpt_changes(),
|
||||
vec![
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
ExcerptEvent::Removed { ids: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
// Widen back to "ONE" — 4 files re-added.
|
||||
perform_incremental_search(search_view, "ONE", cx);
|
||||
assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
|
||||
assert_all_highlights_match_query(search_view, "ONE", cx);
|
||||
assert_eq!(
|
||||
take_excerpt_changes(),
|
||||
vec![
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
ExcerptEvent::Added { excerpts: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
// Narrowing when all files still match — zero excerpt events.
|
||||
// "usize" matches all 5 .rs files; "usize =" is narrower but still in every file.
|
||||
perform_search(search_view, "usize", cx);
|
||||
assert_eq!(read_match_count(search_view, cx), 6);
|
||||
events.borrow_mut().clear();
|
||||
perform_incremental_search(search_view, "usize =", cx);
|
||||
assert_eq!(read_match_count(search_view, cx), 6);
|
||||
assert_eq!(events.borrow().len(), 0);
|
||||
|
||||
// Merged-excerpt narrowing: "target" matches lines 5 and 10 in big.txt,
|
||||
// whose context lines merge into one excerpt [3..12]. Narrowing to
|
||||
// "targeted" shrinks context to [3..7] ⊂ [3..12] — the existing excerpt
|
||||
// must be kept with zero events.
|
||||
perform_search(search_view, "target", cx);
|
||||
assert_all_highlights_match_query(search_view, "target", cx);
|
||||
assert_eq!(read_match_count(search_view, cx), 2);
|
||||
take_excerpt_changes();
|
||||
|
||||
perform_incremental_search(search_view, "targeted", cx);
|
||||
assert_all_highlights_match_query(search_view, "targeted", cx);
|
||||
assert_eq!(read_match_count(search_view, cx), 1);
|
||||
assert_eq!(take_excerpt_changes(), Vec::new());
|
||||
cx.background_executor.run_until_parked();
|
||||
}
|
||||
}
|
||||
@@ -357,8 +357,7 @@ impl VsCodeSettings {
|
||||
fn search_content(&self) -> Option<SearchSettingsContent> {
|
||||
skip_default(SearchSettingsContent {
|
||||
include_ignored: self.read_bool("search.useIgnoreFiles"),
|
||||
search_on_input: self.read_bool("search.searchOnType"),
|
||||
..SearchSettingsContent::default()
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -828,8 +828,6 @@ pub struct SearchSettingsContent {
|
||||
pub regex: Option<bool>,
|
||||
/// Whether to center the cursor on each search match when navigating.
|
||||
pub center_on_match: Option<bool>,
|
||||
/// Whether to search on input in project search.
|
||||
pub search_on_input: Option<bool>,
|
||||
}
|
||||
|
||||
#[with_fallible_options]
|
||||
|
||||
@@ -2999,7 +2999,7 @@ fn languages_and_tools_page(cx: &App) -> SettingsPage {
|
||||
}
|
||||
|
||||
fn search_and_files_page() -> SettingsPage {
|
||||
fn search_section() -> [SettingsPageItem; 10] {
|
||||
fn search_section() -> [SettingsPageItem; 9] {
|
||||
[
|
||||
SettingsPageItem::SectionHeader("Search"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
@@ -3133,29 +3133,6 @@ fn search_and_files_page() -> SettingsPage {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Search on Input",
|
||||
description: "Whether to search on input in project search.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("editor.search.search_on_input"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.editor
|
||||
.search
|
||||
.as_ref()
|
||||
.and_then(|search| search.search_on_input.as_ref())
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.editor
|
||||
.search
|
||||
.get_or_insert_default()
|
||||
.search_on_input = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Seed Search Query From Cursor",
|
||||
description: "When to populate a new search's query based on the text under the cursor.",
|
||||
|
||||
@@ -3279,7 +3279,13 @@ Non-negative `integer` values
|
||||
|
||||
- Description: Whether to search on input in project search.
|
||||
- Setting: `search_on_input`
|
||||
- Default: `true`
|
||||
- Default: `false`
|
||||
|
||||
### Search On Input Debounce Ms
|
||||
|
||||
- Description: Debounce time in milliseconds for search on input in project search. Set to 0 to disable debouncing.
|
||||
- Setting: `search_on_input_debounce_ms`
|
||||
- Default: `200`
|
||||
|
||||
### Center On Match
|
||||
|
||||
|
||||
Reference in new issue
Block a user