Use new multibuffer excerpts in project search (#27893)
Follow-up of https://github.com/zed-industries/zed/pull/27876 Closes https://github.com/zed-industries/zed/issues/13513 Release Notes: - Improved multi buffer excerpts to merge when expanded --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
b4af5b2ce0
commit
8a6ed4a2ca
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -8665,7 +8665,6 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger 0.11.8",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
|
@ -28,7 +28,6 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
|
@ -13,7 +13,6 @@ use buffer_diff::{
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet};
|
||||
use futures::{SinkExt, channel::mpsc};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@ -1569,32 +1568,70 @@ impl MultiBuffer {
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
|
||||
|
||||
let excerpt_ranges = ranges.into_iter().map(|range| {
|
||||
let start_row = range.start.row.saturating_sub(context_line_count);
|
||||
let start = Point::new(start_row, 0);
|
||||
let end_row = (range.end.row + context_line_count).min(buffer_snapshot.max_point().row);
|
||||
let end = Point::new(end_row, buffer_snapshot.line_len(end_row));
|
||||
ExcerptRange {
|
||||
context: start..end,
|
||||
primary: range,
|
||||
}
|
||||
});
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
self.set_excerpt_ranges_for_path(
|
||||
path,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
self.set_excerpt_ranges_for_path(path, buffer, excerpt_ranges.collect(), cx)
|
||||
pub fn set_anchored_excerpts_for_path(
|
||||
&self,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: Vec<Range<text::Anchor>>,
|
||||
context_line_count: u32,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<Range<Anchor>>> {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let path_key = PathKey::for_buffer(&buffer, cx);
|
||||
cx.spawn(async move |multi_buffer, cx| {
|
||||
let snapshot = buffer_snapshot.clone();
|
||||
let (excerpt_ranges, new, counts) = cx
|
||||
.background_spawn(async move {
|
||||
let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
|
||||
let excerpt_ranges =
|
||||
build_excerpt_ranges(ranges, context_line_count, &snapshot);
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
(excerpt_ranges, new, counts)
|
||||
})
|
||||
.await;
|
||||
|
||||
multi_buffer
|
||||
.update(cx, move |multi_buffer, cx| {
|
||||
let (ranges, _) = multi_buffer.set_excerpt_ranges_for_path(
|
||||
path_key,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
);
|
||||
ranges
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets excerpts, returns `true` if at least one new excerpt was added.
|
||||
pub fn set_excerpt_ranges_for_path(
|
||||
fn set_excerpt_ranges_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: Vec<ExcerptRange<Point>>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
counts: Vec<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let (new, counts) = self.merge_excerpt_ranges(&ranges);
|
||||
let (excerpt_ids, added_a_new_excerpt) =
|
||||
self.update_path_excerpts(path, buffer, &buffer_snapshot, new, cx);
|
||||
|
||||
@ -1614,9 +1651,8 @@ impl MultiBuffer {
|
||||
(result, added_a_new_excerpt)
|
||||
}
|
||||
|
||||
fn merge_excerpt_ranges(
|
||||
&self,
|
||||
expanded_ranges: &Vec<ExcerptRange<Point>>,
|
||||
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();
|
||||
let mut counts: Vec<usize> = Vec::new();
|
||||
@ -1683,7 +1719,9 @@ impl MultiBuffer {
|
||||
(None, Some(_)) => {
|
||||
let existing_id = existing_iter.next().unwrap();
|
||||
let locator = snapshot.excerpt_locator_for_id(existing_id);
|
||||
let existing_excerpt = excerpts_cursor.item().unwrap();
|
||||
let Some(existing_excerpt) = excerpts_cursor.item() else {
|
||||
break;
|
||||
};
|
||||
excerpts_cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
let existing_end = existing_excerpt
|
||||
.range
|
||||
@ -1799,92 +1837,6 @@ impl MultiBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_multiple_excerpts_with_context_lines(
|
||||
&self,
|
||||
buffers_with_ranges: Vec<(Entity<Buffer>, Vec<Range<text::Anchor>>)>,
|
||||
context_line_count: u32,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<Range<Anchor>>> {
|
||||
use futures::StreamExt;
|
||||
|
||||
let (excerpt_ranges_tx, mut excerpt_ranges_rx) = mpsc::channel(256);
|
||||
|
||||
let mut buffer_ids = Vec::with_capacity(buffers_with_ranges.len());
|
||||
|
||||
for (buffer, ranges) in buffers_with_ranges {
|
||||
let (buffer_id, buffer_snapshot) =
|
||||
buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
|
||||
|
||||
buffer_ids.push(buffer_id);
|
||||
|
||||
cx.background_spawn({
|
||||
let mut excerpt_ranges_tx = excerpt_ranges_tx.clone();
|
||||
|
||||
async move {
|
||||
let (excerpt_ranges, counts) =
|
||||
build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
|
||||
excerpt_ranges_tx
|
||||
.send((buffer_id, buffer.clone(), ranges, excerpt_ranges, counts))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let mut results_by_buffer_id = HashMap::default();
|
||||
while let Some((buffer_id, buffer, ranges, excerpt_ranges, range_counts)) =
|
||||
excerpt_ranges_rx.next().await
|
||||
{
|
||||
results_by_buffer_id
|
||||
.insert(buffer_id, (buffer, ranges, excerpt_ranges, range_counts));
|
||||
}
|
||||
|
||||
let mut multi_buffer_ranges = Vec::default();
|
||||
'outer: for buffer_id in buffer_ids {
|
||||
let Some((buffer, ranges, excerpt_ranges, range_counts)) =
|
||||
results_by_buffer_id.remove(&buffer_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut ranges = ranges.into_iter();
|
||||
let mut range_counts = range_counts.into_iter();
|
||||
for excerpt_ranges in excerpt_ranges.chunks(100) {
|
||||
let excerpt_ids = match this.update(cx, |this, cx| {
|
||||
this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx)
|
||||
}) {
|
||||
Ok(excerpt_ids) => excerpt_ids,
|
||||
Err(_) => continue 'outer,
|
||||
};
|
||||
|
||||
for (excerpt_id, range_count) in
|
||||
excerpt_ids.into_iter().zip(range_counts.by_ref())
|
||||
{
|
||||
for range in ranges.by_ref().take(range_count) {
|
||||
let start = Anchor {
|
||||
buffer_id: Some(buffer_id),
|
||||
excerpt_id,
|
||||
text_anchor: range.start,
|
||||
diff_base_anchor: None,
|
||||
};
|
||||
let end = Anchor {
|
||||
buffer_id: Some(buffer_id),
|
||||
excerpt_id,
|
||||
text_anchor: range.end,
|
||||
diff_base_anchor: None,
|
||||
};
|
||||
multi_buffer_ranges.push(start..end);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multi_buffer_ranges
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_excerpts_after<O>(
|
||||
&mut self,
|
||||
prev_excerpt_id: ExcerptId,
|
||||
@ -3452,6 +3404,26 @@ impl MultiBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_excerpt_ranges(
|
||||
ranges: impl IntoIterator<Item = Range<Point>>,
|
||||
context_line_count: u32,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
) -> Vec<ExcerptRange<Point>> {
|
||||
ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start_row = range.start.row.saturating_sub(context_line_count);
|
||||
let start = Point::new(start_row, 0);
|
||||
let end_row = (range.end.row + context_line_count).min(buffer_snapshot.max_point().row);
|
||||
let end = Point::new(end_row, buffer_snapshot.line_len(end_row));
|
||||
ExcerptRange {
|
||||
context: start..end,
|
||||
primary: range,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl MultiBuffer {
|
||||
pub fn build_simple(text: &str, cx: &mut gpui::App) -> Entity<Self> {
|
||||
@ -7772,47 +7744,3 @@ impl From<ExcerptId> for EntityId {
|
||||
EntityId::from(id.0 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_excerpt_ranges<T>(
|
||||
buffer: &BufferSnapshot,
|
||||
ranges: &[Range<T>],
|
||||
context_line_count: u32,
|
||||
) -> (Vec<ExcerptRange<Point>>, Vec<usize>)
|
||||
where
|
||||
T: text::ToPoint,
|
||||
{
|
||||
let max_point = buffer.max_point();
|
||||
let mut range_counts = Vec::new();
|
||||
let mut excerpt_ranges = Vec::new();
|
||||
let mut range_iter = ranges
|
||||
.iter()
|
||||
.map(|range| range.start.to_point(buffer)..range.end.to_point(buffer))
|
||||
.peekable();
|
||||
while let Some(range) = range_iter.next() {
|
||||
let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0);
|
||||
let row = (range.end.row + context_line_count).min(max_point.row);
|
||||
let mut excerpt_end = Point::new(row, buffer.line_len(row));
|
||||
|
||||
let mut ranges_in_excerpt = 1;
|
||||
|
||||
while let Some(next_range) = range_iter.peek() {
|
||||
if next_range.start.row <= excerpt_end.row + context_line_count {
|
||||
let row = (next_range.end.row + context_line_count).min(max_point.row);
|
||||
excerpt_end = Point::new(row, buffer.line_len(row));
|
||||
|
||||
ranges_in_excerpt += 1;
|
||||
range_iter.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
excerpt_ranges.push(ExcerptRange {
|
||||
context: excerpt_start..excerpt_end,
|
||||
primary: range,
|
||||
});
|
||||
range_counts.push(ranges_in_excerpt);
|
||||
}
|
||||
|
||||
(excerpt_ranges, range_counts)
|
||||
}
|
||||
|
@ -781,7 +781,7 @@ fn test_expand_excerpts(cx: &mut App) {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
|
||||
async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
|
||||
let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
|
||||
let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
|
||||
@ -797,15 +797,39 @@ async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext)
|
||||
];
|
||||
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let anchor_ranges = multibuffer
|
||||
let anchor_ranges_1 = multibuffer
|
||||
.update(cx, |multibuffer, cx| {
|
||||
multibuffer.push_multiple_excerpts_with_context_lines(
|
||||
vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
|
||||
2,
|
||||
cx,
|
||||
)
|
||||
multibuffer.set_anchored_excerpts_for_path(buffer_1.clone(), ranges_1, 2, cx)
|
||||
})
|
||||
.await;
|
||||
let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
assert_eq!(
|
||||
anchor_ranges_1
|
||||
.iter()
|
||||
.map(|range| range.to_point(&snapshot_1))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Point::new(2, 2)..Point::new(3, 2),
|
||||
Point::new(6, 1)..Point::new(6, 3),
|
||||
Point::new(11, 0)..Point::new(11, 0),
|
||||
]
|
||||
);
|
||||
let anchor_ranges_2 = multibuffer
|
||||
.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_anchored_excerpts_for_path(buffer_2.clone(), ranges_2, 2, cx)
|
||||
})
|
||||
.await;
|
||||
let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
assert_eq!(
|
||||
anchor_ranges_2
|
||||
.iter()
|
||||
.map(|range| range.to_point(&snapshot_2))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Point::new(16, 1)..Point::new(17, 1),
|
||||
Point::new(22, 0)..Point::new(22, 2)
|
||||
]
|
||||
);
|
||||
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
assert_eq!(
|
||||
@ -841,20 +865,6 @@ async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext)
|
||||
"mmmm", //
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
anchor_ranges
|
||||
.iter()
|
||||
.map(|range| range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Point::new(2, 2)..Point::new(3, 2),
|
||||
Point::new(6, 1)..Point::new(6, 3),
|
||||
Point::new(11, 0)..Point::new(11, 0),
|
||||
Point::new(16, 1)..Point::new(17, 1),
|
||||
Point::new(22, 0)..Point::new(22, 2)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -15,6 +15,7 @@ use std::{
|
||||
use text::Anchor;
|
||||
use util::paths::PathMatcher;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SearchResult {
|
||||
Buffer {
|
||||
buffer: Entity<Buffer>,
|
||||
|
@ -9,7 +9,7 @@ use editor::{
|
||||
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN,
|
||||
MultiBuffer, actions::SelectAll, items::active_match_index, scroll::Autoscroll,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use futures::{StreamExt, stream::FuturesOrdered};
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point,
|
||||
@ -260,16 +260,18 @@ impl ProjectSearch {
|
||||
self.search_id += 1;
|
||||
self.active_query = Some(query);
|
||||
self.match_ranges.clear();
|
||||
self.pending_search = Some(cx.spawn(async move |this, cx| {
|
||||
self.pending_search = Some(cx.spawn(async move |project_search, cx| {
|
||||
let mut matches = pin!(search.ready_chunks(1024));
|
||||
let this = this.upgrade()?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.match_ranges.clear();
|
||||
this.excerpts.update(cx, |this, cx| this.clear(cx));
|
||||
this.no_results = Some(true);
|
||||
this.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;
|
||||
while let Some(results) = matches.next().await {
|
||||
@ -285,35 +287,43 @@ impl ProjectSearch {
|
||||
}
|
||||
}
|
||||
|
||||
let match_ranges = this
|
||||
.update(cx, |this, cx| {
|
||||
this.excerpts.update(cx, |excerpts, cx| {
|
||||
excerpts.push_multiple_excerpts_with_context_lines(
|
||||
buffers_with_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
let excerpts = project_search
|
||||
.update(cx, |project_search, _| project_search.excerpts.clone())
|
||||
.ok()?;
|
||||
let mut new_ranges = excerpts
|
||||
.update(cx, |excerpts, cx| {
|
||||
buffers_with_ranges
|
||||
.into_iter()
|
||||
.map(|(buffer, ranges)| {
|
||||
excerpts.set_anchored_excerpts_for_path(
|
||||
buffer,
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
.ok()?;
|
||||
while let Some(new_ranges) = new_ranges.next().await {
|
||||
project_search
|
||||
.update(cx, |project_search, _| {
|
||||
project_search.match_ranges.extend(new_ranges);
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
}
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.match_ranges.extend(match_ranges);
|
||||
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()?;
|
||||
}
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if !this.match_ranges.is_empty() {
|
||||
this.no_results = Some(false);
|
||||
}
|
||||
this.limit_reached = limit_reached;
|
||||
this.pending_search.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
None
|
||||
}));
|
||||
|
Loading…
x
Reference in New Issue
Block a user