Search in selections (#10831)
Release Notes: - Adding [#8617 ](https://github.com/zed-industries/zed/issues/8617) --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
428c143fbb
commit
c7c19609b3
13 changed files with 818 additions and 69 deletions
|
@ -3740,6 +3740,62 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns excerpts overlapping the given ranges. If range spans multiple excerpts returns one range for each excerpt
|
||||
pub fn excerpts_in_ranges(
|
||||
&self,
|
||||
ranges: impl IntoIterator<Item = Range<Anchor>>,
|
||||
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut next_range = move |cursor: &mut Cursor<Excerpt, usize>| {
|
||||
let range = ranges.next();
|
||||
if let Some(range) = range.as_ref() {
|
||||
cursor.seek_forward(&range.start, Bias::Right, &());
|
||||
}
|
||||
|
||||
range
|
||||
};
|
||||
let mut range = next_range(&mut cursor);
|
||||
|
||||
iter::from_fn(move || {
|
||||
if range.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if range.as_ref().unwrap().is_empty() || *cursor.start() >= range.as_ref().unwrap().end
|
||||
{
|
||||
range = next_range(&mut cursor);
|
||||
if range.is_none() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.item().map(|excerpt| {
|
||||
let multibuffer_excerpt = MultiBufferExcerpt::new(&excerpt, *cursor.start());
|
||||
|
||||
let multibuffer_excerpt_range = multibuffer_excerpt
|
||||
.map_range_from_buffer(excerpt.range.context.to_offset(&excerpt.buffer));
|
||||
|
||||
let overlap_range = cmp::max(
|
||||
range.as_ref().unwrap().start,
|
||||
multibuffer_excerpt_range.start,
|
||||
)
|
||||
..cmp::min(range.as_ref().unwrap().end, multibuffer_excerpt_range.end);
|
||||
|
||||
let overlap_range = multibuffer_excerpt.map_range_to_buffer(overlap_range);
|
||||
|
||||
if multibuffer_excerpt_range.end <= range.as_ref().unwrap().end {
|
||||
cursor.next(&());
|
||||
} else {
|
||||
range = next_range(&mut cursor);
|
||||
}
|
||||
|
||||
(excerpt.id, &excerpt.buffer, overlap_range)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remote_selections_in_range<'a>(
|
||||
&'a self,
|
||||
range: &'a Range<Anchor>,
|
||||
|
@ -6076,4 +6132,415 @@ mod tests {
|
|||
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_no_ranges(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
|
||||
let mut excerpts = snapshot.excerpts_in_ranges(iter::from_fn(|| None));
|
||||
|
||||
assert!(excerpts.next().is_none());
|
||||
}
|
||||
|
||||
fn validate_excerpts(
|
||||
actual: &Vec<(ExcerptId, BufferId, Range<Anchor>)>,
|
||||
expected: &Vec<(ExcerptId, BufferId, Range<Anchor>)>,
|
||||
) {
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
|
||||
actual
|
||||
.into_iter()
|
||||
.zip(expected)
|
||||
.map(|(actual, expected)| {
|
||||
assert_eq!(actual.0, expected.0);
|
||||
assert_eq!(actual.1, expected.1);
|
||||
assert_eq!(actual.2.start, expected.2.start);
|
||||
assert_eq!(actual.2.end, expected.2.end);
|
||||
})
|
||||
.collect_vec();
|
||||
}
|
||||
|
||||
fn map_range_from_excerpt(
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
excerpt_id: ExcerptId,
|
||||
excerpt_buffer: &BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Range<Anchor> {
|
||||
snapshot
|
||||
.anchor_in_excerpt(excerpt_id, excerpt_buffer.anchor_before(range.start))
|
||||
.unwrap()
|
||||
..snapshot
|
||||
.anchor_in_excerpt(excerpt_id, excerpt_buffer.anchor_after(range.end))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn make_expected_excerpt_info(
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
cx: &mut AppContext,
|
||||
excerpt_id: ExcerptId,
|
||||
buffer: &Model<Buffer>,
|
||||
range: Range<usize>,
|
||||
) -> (ExcerptId, BufferId, Range<Anchor>) {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.read(cx).remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, &buffer.read(cx).snapshot(), range),
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_range_inside_the_excerpt(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_len = buffer_1.read(cx).len();
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
let mut expected_excerpt_id = ExcerptId(0);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
expected_excerpt_id = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
|
||||
let range = snapshot
|
||||
.anchor_in_excerpt(expected_excerpt_id, buffer_1.read(cx).anchor_before(1))
|
||||
.unwrap()
|
||||
..snapshot
|
||||
.anchor_in_excerpt(
|
||||
expected_excerpt_id,
|
||||
buffer_1.read(cx).anchor_after(buffer_len / 2),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_excerpts = vec![make_expected_excerpt_info(
|
||||
&snapshot,
|
||||
cx,
|
||||
expected_excerpt_id,
|
||||
&buffer_1,
|
||||
1..(buffer_len / 2),
|
||||
)];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
validate_excerpts(&excerpts, &expected_excerpts);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_range_crosses_excerpts_boundary(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_len = buffer_1.read(cx).len();
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
let mut excerpt_1_id = ExcerptId(0);
|
||||
let mut excerpt_2_id = ExcerptId(0);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
excerpt_1_id = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
excerpt_2_id = multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
|
||||
let expected_range = snapshot
|
||||
.anchor_in_excerpt(
|
||||
excerpt_1_id,
|
||||
buffer_1.read(cx).anchor_before(buffer_len / 2),
|
||||
)
|
||||
.unwrap()
|
||||
..snapshot
|
||||
.anchor_in_excerpt(excerpt_2_id, buffer_2.read(cx).anchor_after(buffer_len / 2))
|
||||
.unwrap();
|
||||
|
||||
let expected_excerpts = vec![
|
||||
make_expected_excerpt_info(
|
||||
&snapshot,
|
||||
cx,
|
||||
excerpt_1_id,
|
||||
&buffer_1,
|
||||
(buffer_len / 2)..buffer_len,
|
||||
),
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_2_id, &buffer_2, 0..buffer_len / 2),
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
validate_excerpts(&excerpts, &expected_excerpts);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_range_encloses_excerpt(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'r'), cx));
|
||||
let buffer_len = buffer_1.read(cx).len();
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
let mut excerpt_1_id = ExcerptId(0);
|
||||
let mut excerpt_2_id = ExcerptId(0);
|
||||
let mut excerpt_3_id = ExcerptId(0);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
excerpt_1_id = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
excerpt_2_id = multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
excerpt_3_id = multibuffer.push_excerpts(
|
||||
buffer_3.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_3.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
|
||||
let expected_range = snapshot
|
||||
.anchor_in_excerpt(
|
||||
excerpt_1_id,
|
||||
buffer_1.read(cx).anchor_before(buffer_len / 2),
|
||||
)
|
||||
.unwrap()
|
||||
..snapshot
|
||||
.anchor_in_excerpt(excerpt_3_id, buffer_3.read(cx).anchor_after(buffer_len / 2))
|
||||
.unwrap();
|
||||
|
||||
let expected_excerpts = vec![
|
||||
make_expected_excerpt_info(
|
||||
&snapshot,
|
||||
cx,
|
||||
excerpt_1_id,
|
||||
&buffer_1,
|
||||
(buffer_len / 2)..buffer_len,
|
||||
),
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_2_id, &buffer_2, 0..buffer_len),
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_3_id, &buffer_3, 0..buffer_len / 2),
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
validate_excerpts(&excerpts, &expected_excerpts);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_multiple_ranges(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_len = buffer_1.read(cx).len();
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
let mut excerpt_1_id = ExcerptId(0);
|
||||
let mut excerpt_2_id = ExcerptId(0);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
excerpt_1_id = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
excerpt_2_id = multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
|
||||
let ranges = vec![
|
||||
1..(buffer_len / 4),
|
||||
(buffer_len / 3)..(buffer_len / 2),
|
||||
(buffer_len / 4 * 3)..(buffer_len),
|
||||
];
|
||||
|
||||
let expected_excerpts = ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_1_id, &buffer_1, range.clone())
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
let ranges = ranges.into_iter().map(|range| {
|
||||
map_range_from_excerpt(
|
||||
&snapshot,
|
||||
excerpt_1_id,
|
||||
&buffer_1.read(cx).snapshot(),
|
||||
range,
|
||||
)
|
||||
});
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges)
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
validate_excerpts(&excerpts, &expected_excerpts);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpts_in_ranges_range_ends_at_excerpt_end(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_len = buffer_1.read(cx).len();
|
||||
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
|
||||
let mut excerpt_1_id = ExcerptId(0);
|
||||
let mut excerpt_2_id = ExcerptId(0);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
excerpt_1_id = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
excerpt_2_id = multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer_2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
)[0];
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
|
||||
let ranges = [0..buffer_len, (buffer_len / 3)..(buffer_len / 2)];
|
||||
|
||||
let expected_excerpts = vec![
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_1_id, &buffer_1, ranges[0].clone()),
|
||||
make_expected_excerpt_info(&snapshot, cx, excerpt_2_id, &buffer_2, ranges[1].clone()),
|
||||
];
|
||||
|
||||
let ranges = [
|
||||
map_range_from_excerpt(
|
||||
&snapshot,
|
||||
excerpt_1_id,
|
||||
&buffer_1.read(cx).snapshot(),
|
||||
ranges[0].clone(),
|
||||
),
|
||||
map_range_from_excerpt(
|
||||
&snapshot,
|
||||
excerpt_2_id,
|
||||
&buffer_2.read(cx).snapshot(),
|
||||
ranges[1].clone(),
|
||||
),
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges.into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
validate_excerpts(&excerpts, &expected_excerpts);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue