diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 0df196bb9829d..828b39967d9e1 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; use futures::{channel::mpsc, SinkExt}; -use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext}; +use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext, Task}; use itertools::Itertools; use language::{ language_settings::{language_settings, LanguageSettings}, @@ -1130,66 +1130,6 @@ impl MultiBuffer { } } - pub fn stream_excerpts_with_context_lines( - &mut self, - buffer: Model, - ranges: Vec>, - context_line_count: u32, - cx: &mut ModelContext, - ) -> mpsc::Receiver> { - let (buffer_id, buffer_snapshot) = - buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot())); - - let (mut tx, rx) = mpsc::channel(256); - cx.spawn(move |this, mut cx| async move { - let mut excerpt_ranges = Vec::new(); - let mut range_counts = Vec::new(); - cx.background_executor() - .scoped(|scope| { - scope.spawn(async { - let (ranges, counts) = - build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); - excerpt_ranges = ranges; - range_counts = counts; - }); - }) - .await; - - 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(&mut cx, |this, cx| { - this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) - }) { - Ok(excerpt_ids) => excerpt_ids, - Err(_) => return, - }; - - 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, - }; - let end = Anchor { - buffer_id: Some(buffer_id), - excerpt_id, - text_anchor: range.end, - }; - if tx.send(start..end).await.is_err() { - break; - } - } - } - } - }) - .detach(); - - rx - } - pub fn push_excerpts( &mut self, buffer: Model, @@ -1239,6 +1179,91 @@ impl MultiBuffer { anchor_ranges } + pub fn push_multiple_excerpts_with_context_lines( + &mut self, + buffers_with_ranges: Vec<(Model, Vec>)>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> Task>> { + 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_executor() + .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(move |this, mut cx| async move { + 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(&mut 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, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id, + text_anchor: range.end, + }; + multi_buffer_ranges.push(start..end); + } + } + } + } + + multi_buffer_ranges + }) + } + pub fn insert_excerpts_after( &mut self, prev_excerpt_id: ExcerptId, @@ -5052,7 +5077,6 @@ where #[cfg(test)] mod tests { use super::*; - use futures::StreamExt; use gpui::{AppContext, Context, TestAppContext}; use language::{Buffer, Rope}; use parking_lot::RwLock; @@ -5601,41 +5625,67 @@ mod tests { ); } - #[gpui::test] - async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { - let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx)); - let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite)); - let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { - let snapshot = buffer.read(cx); - let ranges = vec![ - snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), - snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), - snapshot.anchor_before(Point::new(15, 0)) - ..snapshot.anchor_before(Point::new(15, 0)), - ]; - multibuffer.stream_excerpts_with_context_lines(buffer.clone(), ranges, 2, cx) - }); + #[gpui::test(iterations = 100)] + async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) { + let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx)); + let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(15, 4, 'a'), cx)); + let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot()); + let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot()); + let ranges_1 = vec![ + snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)), + snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)), + snapshot_1.anchor_before(Point::new(15, 0)) + ..snapshot_1.anchor_before(Point::new(15, 0)), + ]; + let ranges_2 = vec![ + snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)), + snapshot_2.anchor_before(Point::new(10, 0)) + ..snapshot_2.anchor_before(Point::new(10, 2)), + ]; - let anchor_ranges = anchor_ranges.collect::>().await; + let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite)); + let anchor_ranges = 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, + ) + }) + .await; let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx)); assert_eq!( snapshot.text(), concat!( - "bbb\n", // + "bbb\n", // buffer_1 "ccc\n", // - "ddd\n", // - "eee\n", // + "ddd\n", // <-- excerpt 1 + "eee\n", // <-- excerpt 1 "fff\n", // "ggg\n", // - "hhh\n", // + "hhh\n", // <-- excerpt 2 "iii\n", // "jjj\n", // + // "nnn\n", // "ooo\n", // - "ppp\n", // + "ppp\n", // <-- excerpt 3 "qqq\n", // - "rrr", // + "rrr\n", // + // + "aaaa\n", // buffer 2 + "bbbb\n", // + "cccc\n", // <-- excerpt 4 + "dddd\n", // <-- excerpt 4 + "eeee\n", // + "ffff\n", // + // + "iiii\n", // + "jjjj\n", // + "kkkk\n", // <-- excerpt 5 + "llll\n", // + "mmmm", // ) ); @@ -5647,7 +5697,9 @@ mod tests { 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(11, 0)..Point::new(11, 0), + Point::new(16, 1)..Point::new(17, 1), + Point::new(22, 0)..Point::new(22, 2) ] ); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d5b719a657628..ea94d27daf61d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -263,54 +263,35 @@ impl ProjectSearch { let mut limit_reached = false; while let Some(results) = matches.next().await { - let tasks = results - .into_iter() - .map(|result| { - let this = this.clone(); - - cx.spawn(|mut cx| async move { - match result { - project::search::SearchResult::Buffer { buffer, ranges } => { - let mut match_ranges_rx = - this.update(&mut cx, |this, cx| { - this.excerpts.update(cx, |excerpts, cx| { - excerpts.stream_excerpts_with_context_lines( - buffer, - ranges, - editor::DEFAULT_MULTIBUFFER_CONTEXT, - cx, - ) - }) - })?; - - let mut match_ranges = vec![]; - while let Some(range) = match_ranges_rx.next().await { - match_ranges.push(range); - } - anyhow::Ok((match_ranges, false)) - } - project::search::SearchResult::LimitReached => { - anyhow::Ok((vec![], true)) - } - } - }) - }) - .collect::>(); - - let result_ranges = futures::future::join_all(tasks).await; - let mut combined_ranges = vec![]; - for (ranges, result_limit_reached) in result_ranges.into_iter().flatten() { - combined_ranges.extend(ranges); - if result_limit_reached { - limit_reached = result_limit_reached; + let mut buffers_with_ranges = Vec::with_capacity(results.len()); + for result in results { + match result { + project::search::SearchResult::Buffer { buffer, ranges } => { + buffers_with_ranges.push((buffer, ranges)); + } + project::search::SearchResult::LimitReached => { + limit_reached = true; + } } } + + let match_ranges = this + .update(&mut cx, |this, cx| { + this.excerpts.update(cx, |excerpts, cx| { + excerpts.push_multiple_excerpts_with_context_lines( + buffers_with_ranges, + editor::DEFAULT_MULTIBUFFER_CONTEXT, + cx, + ) + }) + }) + .ok()? + .await; + this.update(&mut cx, |this, cx| { - if !combined_ranges.is_empty() { - this.no_results = Some(false); - this.match_ranges.extend(combined_ranges); - cx.notify(); - } + this.no_results = Some(false); + this.match_ranges.extend(match_ranges); + cx.notify(); }) .ok()?; } @@ -2745,7 +2726,7 @@ pub mod tests { search_view .results_editor .update(cx, |editor, cx| editor.display_text(cx)), - "\n\n\nconst TWO: usize = one::ONE + one::ONE;\n\n\n\n\nconst ONE: usize = 1;\n", + "\n\n\nconst ONE: usize = 1;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n", "New search in directory should have a filter that matches a certain directory" ); })