1990 lines
68 KiB
Rust
1990 lines
68 KiB
Rust
use super::*;
|
|
use gpui::{AppContext, Context, TestAppContext};
|
|
use language::{Buffer, Rope};
|
|
use parking_lot::RwLock;
|
|
use rand::prelude::*;
|
|
use settings::SettingsStore;
|
|
use std::env;
|
|
use util::test::sample_text;
|
|
|
|
#[ctor::ctor]
|
|
fn init_logger() {
|
|
if std::env::var("RUST_LOG").is_ok() {
|
|
env_logger::init();
|
|
}
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_singleton(cx: &mut AppContext) {
|
|
let buffer = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
|
let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot.text(), buffer.read(cx).text());
|
|
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
|
|
(0..buffer.read(cx).row_count())
|
|
.map(Some)
|
|
.collect::<Vec<_>>()
|
|
);
|
|
|
|
buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
assert_eq!(snapshot.text(), buffer.read(cx).text());
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
|
|
(0..buffer.read(cx).row_count())
|
|
.map(Some)
|
|
.collect::<Vec<_>>()
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_remote(cx: &mut AppContext) {
|
|
let host_buffer = cx.new_model(|cx| Buffer::local("a", cx));
|
|
let guest_buffer = cx.new_model(|cx| {
|
|
let state = host_buffer.read(cx).to_proto(cx);
|
|
let ops = cx
|
|
.background_executor()
|
|
.block(host_buffer.read(cx).serialize_ops(None, cx));
|
|
let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
|
|
buffer.apply_ops(
|
|
ops.into_iter()
|
|
.map(|op| language::proto::deserialize_operation(op).unwrap()),
|
|
cx,
|
|
);
|
|
buffer
|
|
});
|
|
let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot.text(), "a");
|
|
|
|
guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot.text(), "ab");
|
|
|
|
guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot.text(), "abc");
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_excerpt_boundaries_and_clipping(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(Capability::ReadWrite));
|
|
|
|
let events = Arc::new(RwLock::new(Vec::<Event>::new()));
|
|
multibuffer.update(cx, |_, cx| {
|
|
let events = events.clone();
|
|
cx.subscribe(&multibuffer, move |_, _, event, _| {
|
|
if let Event::Edited { .. } = event {
|
|
events.write().push(event.clone())
|
|
}
|
|
})
|
|
.detach();
|
|
});
|
|
|
|
let subscription = multibuffer.update(cx, |multibuffer, cx| {
|
|
let subscription = multibuffer.subscribe();
|
|
multibuffer.push_excerpts(
|
|
buffer_1.clone(),
|
|
[ExcerptRange {
|
|
context: Point::new(1, 2)..Point::new(2, 5),
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
subscription.consume().into_inner(),
|
|
[Edit {
|
|
old: 0..0,
|
|
new: 0..10
|
|
}]
|
|
);
|
|
|
|
multibuffer.push_excerpts(
|
|
buffer_1.clone(),
|
|
[ExcerptRange {
|
|
context: Point::new(3, 3)..Point::new(4, 4),
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
multibuffer.push_excerpts(
|
|
buffer_2.clone(),
|
|
[ExcerptRange {
|
|
context: Point::new(3, 1)..Point::new(3, 3),
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
assert_eq!(
|
|
subscription.consume().into_inner(),
|
|
[Edit {
|
|
old: 10..10,
|
|
new: 10..22
|
|
}]
|
|
);
|
|
|
|
subscription
|
|
});
|
|
|
|
// Adding excerpts emits an edited event.
|
|
assert_eq!(
|
|
events.read().as_slice(),
|
|
&[
|
|
Event::Edited {
|
|
singleton_buffer_edited: false,
|
|
edited_buffer: None,
|
|
},
|
|
Event::Edited {
|
|
singleton_buffer_edited: false,
|
|
edited_buffer: None,
|
|
},
|
|
Event::Edited {
|
|
singleton_buffer_edited: false,
|
|
edited_buffer: None,
|
|
}
|
|
]
|
|
);
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"bbbb\n", // Preserve newlines
|
|
"ccccc\n", //
|
|
"ddd\n", //
|
|
"eeee\n", //
|
|
"jj" //
|
|
)
|
|
);
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
|
|
[Some(1), Some(2), Some(3), Some(4), Some(3)]
|
|
);
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(2)).collect::<Vec<_>>(),
|
|
[Some(3), Some(4), Some(3)]
|
|
);
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(4)).collect::<Vec<_>>(),
|
|
[Some(3)]
|
|
);
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(5)).collect::<Vec<_>>(),
|
|
[]
|
|
);
|
|
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
|
|
&[
|
|
(MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
|
|
(MultiBufferRow(2), "ddd\neeee".to_string(), false),
|
|
(MultiBufferRow(4), "jj".to_string(), true),
|
|
]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
|
|
&[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
|
|
&[]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
|
|
&[]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
|
|
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
|
|
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
|
|
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
|
|
&[(MultiBufferRow(4), "jj".to_string(), true)]
|
|
);
|
|
assert_eq!(
|
|
boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
|
|
&[]
|
|
);
|
|
|
|
buffer_1.update(cx, |buffer, cx| {
|
|
let text = "\n";
|
|
buffer.edit(
|
|
[
|
|
(Point::new(0, 0)..Point::new(0, 0), text),
|
|
(Point::new(2, 1)..Point::new(2, 3), text),
|
|
],
|
|
None,
|
|
cx,
|
|
);
|
|
});
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"bbbb\n", // Preserve newlines
|
|
"c\n", //
|
|
"cc\n", //
|
|
"ddd\n", //
|
|
"eeee\n", //
|
|
"jj" //
|
|
)
|
|
);
|
|
|
|
assert_eq!(
|
|
subscription.consume().into_inner(),
|
|
[Edit {
|
|
old: 6..8,
|
|
new: 6..7
|
|
}]
|
|
);
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(
|
|
snapshot.clip_point(Point::new(0, 5), Bias::Left),
|
|
Point::new(0, 4)
|
|
);
|
|
assert_eq!(
|
|
snapshot.clip_point(Point::new(0, 5), Bias::Right),
|
|
Point::new(0, 4)
|
|
);
|
|
assert_eq!(
|
|
snapshot.clip_point(Point::new(5, 1), Bias::Right),
|
|
Point::new(5, 1)
|
|
);
|
|
assert_eq!(
|
|
snapshot.clip_point(Point::new(5, 2), Bias::Right),
|
|
Point::new(5, 2)
|
|
);
|
|
assert_eq!(
|
|
snapshot.clip_point(Point::new(5, 3), Bias::Right),
|
|
Point::new(5, 2)
|
|
);
|
|
|
|
let snapshot = multibuffer.update(cx, |multibuffer, cx| {
|
|
let (buffer_2_excerpt_id, _) = multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
|
|
multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
|
|
multibuffer.snapshot(cx)
|
|
});
|
|
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"bbbb\n", // Preserve newlines
|
|
"c\n", //
|
|
"cc\n", //
|
|
"ddd\n", //
|
|
"eeee", //
|
|
)
|
|
);
|
|
|
|
fn boundaries_in_range(
|
|
range: Range<Point>,
|
|
snapshot: &MultiBufferSnapshot,
|
|
) -> Vec<(MultiBufferRow, String, bool)> {
|
|
snapshot
|
|
.excerpt_boundaries_in_range(range)
|
|
.filter_map(|boundary| {
|
|
let starts_new_buffer = boundary.starts_new_buffer();
|
|
boundary.next.map(|next| {
|
|
(
|
|
boundary.row,
|
|
next.buffer
|
|
.text_for_range(next.range.context)
|
|
.collect::<String>(),
|
|
starts_new_buffer,
|
|
)
|
|
})
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_excerpt_events(cx: &mut AppContext) {
|
|
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
|
|
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
|
|
|
|
let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
let follower_edit_event_count = Arc::new(RwLock::new(0));
|
|
|
|
follower_multibuffer.update(cx, |_, cx| {
|
|
let follower_edit_event_count = follower_edit_event_count.clone();
|
|
cx.subscribe(
|
|
&leader_multibuffer,
|
|
move |follower, _, event, cx| match event.clone() {
|
|
Event::ExcerptsAdded {
|
|
buffer,
|
|
predecessor,
|
|
excerpts,
|
|
} => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
|
|
Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
|
|
Event::Edited { .. } => {
|
|
*follower_edit_event_count.write() += 1;
|
|
}
|
|
_ => {}
|
|
},
|
|
)
|
|
.detach();
|
|
});
|
|
|
|
leader_multibuffer.update(cx, |leader, cx| {
|
|
leader.push_excerpts(
|
|
buffer_1.clone(),
|
|
[
|
|
ExcerptRange {
|
|
context: 0..8,
|
|
primary: None,
|
|
},
|
|
ExcerptRange {
|
|
context: 12..16,
|
|
primary: None,
|
|
},
|
|
],
|
|
cx,
|
|
);
|
|
leader.insert_excerpts_after(
|
|
leader.excerpt_ids()[0],
|
|
buffer_2.clone(),
|
|
[
|
|
ExcerptRange {
|
|
context: 0..5,
|
|
primary: None,
|
|
},
|
|
ExcerptRange {
|
|
context: 10..15,
|
|
primary: None,
|
|
},
|
|
],
|
|
cx,
|
|
)
|
|
});
|
|
assert_eq!(
|
|
leader_multibuffer.read(cx).snapshot(cx).text(),
|
|
follower_multibuffer.read(cx).snapshot(cx).text(),
|
|
);
|
|
assert_eq!(*follower_edit_event_count.read(), 2);
|
|
|
|
leader_multibuffer.update(cx, |leader, cx| {
|
|
let excerpt_ids = leader.excerpt_ids();
|
|
leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
|
|
});
|
|
assert_eq!(
|
|
leader_multibuffer.read(cx).snapshot(cx).text(),
|
|
follower_multibuffer.read(cx).snapshot(cx).text(),
|
|
);
|
|
assert_eq!(*follower_edit_event_count.read(), 3);
|
|
|
|
// Removing an empty set of excerpts is a noop.
|
|
leader_multibuffer.update(cx, |leader, cx| {
|
|
leader.remove_excerpts([], cx);
|
|
});
|
|
assert_eq!(
|
|
leader_multibuffer.read(cx).snapshot(cx).text(),
|
|
follower_multibuffer.read(cx).snapshot(cx).text(),
|
|
);
|
|
assert_eq!(*follower_edit_event_count.read(), 3);
|
|
|
|
// Adding an empty set of excerpts is a noop.
|
|
leader_multibuffer.update(cx, |leader, cx| {
|
|
leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
|
|
});
|
|
assert_eq!(
|
|
leader_multibuffer.read(cx).snapshot(cx).text(),
|
|
follower_multibuffer.read(cx).snapshot(cx).text(),
|
|
);
|
|
assert_eq!(*follower_edit_event_count.read(), 3);
|
|
|
|
leader_multibuffer.update(cx, |leader, cx| {
|
|
leader.clear(cx);
|
|
});
|
|
assert_eq!(
|
|
leader_multibuffer.read(cx).snapshot(cx).text(),
|
|
follower_multibuffer.read(cx).snapshot(cx).text(),
|
|
);
|
|
assert_eq!(*follower_edit_event_count.read(), 4);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_expand_excerpts(cx: &mut AppContext) {
|
|
let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer.push_excerpts_with_context_lines(
|
|
buffer.clone(),
|
|
vec![
|
|
// Note that in this test, this first excerpt
|
|
// does not contain a new line
|
|
Point::new(3, 2)..Point::new(3, 3),
|
|
Point::new(7, 1)..Point::new(7, 3),
|
|
Point::new(15, 0)..Point::new(15, 0),
|
|
],
|
|
1,
|
|
cx,
|
|
)
|
|
});
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"ccc\n", //
|
|
"ddd\n", //
|
|
"eee", //
|
|
"\n", // End of excerpt
|
|
"ggg\n", //
|
|
"hhh\n", //
|
|
"iii", //
|
|
"\n", // End of excerpt
|
|
"ooo\n", //
|
|
"ppp\n", //
|
|
"qqq", // End of excerpt
|
|
)
|
|
);
|
|
drop(snapshot);
|
|
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer.expand_excerpts(
|
|
multibuffer.excerpt_ids(),
|
|
1,
|
|
ExpandExcerptDirection::UpAndDown,
|
|
cx,
|
|
)
|
|
});
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
// Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
|
|
// We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
|
|
// that are tracking excerpt ids.
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"bbb\n", //
|
|
"ccc\n", //
|
|
"ddd\n", //
|
|
"eee\n", //
|
|
"fff\n", // End of excerpt
|
|
"fff\n", //
|
|
"ggg\n", //
|
|
"hhh\n", //
|
|
"iii\n", //
|
|
"jjj\n", // End of excerpt
|
|
"nnn\n", //
|
|
"ooo\n", //
|
|
"ppp\n", //
|
|
"qqq\n", //
|
|
"rrr", // End of excerpt
|
|
)
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
|
|
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| {
|
|
multibuffer.push_excerpts_with_context_lines(
|
|
buffer.clone(),
|
|
vec![
|
|
// Note that in this test, this first excerpt
|
|
// does contain a new line
|
|
Point::new(3, 2)..Point::new(4, 2),
|
|
Point::new(7, 1)..Point::new(7, 3),
|
|
Point::new(15, 0)..Point::new(15, 0),
|
|
],
|
|
2,
|
|
cx,
|
|
)
|
|
});
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
concat!(
|
|
"bbb\n", // Preserve newlines
|
|
"ccc\n", //
|
|
"ddd\n", //
|
|
"eee\n", //
|
|
"fff\n", //
|
|
"ggg\n", //
|
|
"hhh\n", //
|
|
"iii\n", //
|
|
"jjj\n", //
|
|
"nnn\n", //
|
|
"ooo\n", //
|
|
"ppp\n", //
|
|
"qqq\n", //
|
|
"rrr", //
|
|
)
|
|
);
|
|
|
|
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)
|
|
]
|
|
);
|
|
}
|
|
|
|
#[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 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", // buffer_1
|
|
"ccc\n", //
|
|
"ddd\n", // <-- excerpt 1
|
|
"eee\n", // <-- excerpt 1
|
|
"fff\n", //
|
|
"ggg\n", //
|
|
"hhh\n", // <-- excerpt 2
|
|
"iii\n", //
|
|
"jjj\n", //
|
|
//
|
|
"nnn\n", //
|
|
"ooo\n", //
|
|
"ppp\n", // <-- excerpt 3
|
|
"qqq\n", //
|
|
"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", //
|
|
)
|
|
);
|
|
|
|
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]
|
|
fn test_empty_multibuffer(cx: &mut AppContext) {
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot.text(), "");
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
|
|
&[Some(0)]
|
|
);
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(1)).collect::<Vec<_>>(),
|
|
&[]
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
|
|
let buffer = cx.new_model(|cx| Buffer::local("abcd", cx));
|
|
let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
|
let old_snapshot = multibuffer.read(cx).snapshot(cx);
|
|
buffer.update(cx, |buffer, cx| {
|
|
buffer.edit([(0..0, "X")], None, cx);
|
|
buffer.edit([(5..5, "Y")], None, cx);
|
|
});
|
|
let new_snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
assert_eq!(old_snapshot.text(), "abcd");
|
|
assert_eq!(new_snapshot.text(), "XabcdY");
|
|
|
|
assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
|
|
assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
|
|
assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
|
|
assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_multibuffer_anchors(cx: &mut AppContext) {
|
|
let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
|
|
let buffer_2 = cx.new_model(|cx| Buffer::local("efghi", cx));
|
|
let multibuffer = cx.new_model(|cx| {
|
|
let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
|
|
multibuffer.push_excerpts(
|
|
buffer_1.clone(),
|
|
[ExcerptRange {
|
|
context: 0..4,
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
multibuffer.push_excerpts(
|
|
buffer_2.clone(),
|
|
[ExcerptRange {
|
|
context: 0..5,
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
multibuffer
|
|
});
|
|
let old_snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
|
|
assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
|
|
assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
|
|
assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
|
|
assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
|
|
assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
|
|
|
|
buffer_1.update(cx, |buffer, cx| {
|
|
buffer.edit([(0..0, "W")], None, cx);
|
|
buffer.edit([(5..5, "X")], None, cx);
|
|
});
|
|
buffer_2.update(cx, |buffer, cx| {
|
|
buffer.edit([(0..0, "Y")], None, cx);
|
|
buffer.edit([(6..6, "Z")], None, cx);
|
|
});
|
|
let new_snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
assert_eq!(old_snapshot.text(), "abcd\nefghi");
|
|
assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
|
|
|
|
assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
|
|
assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
|
|
assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
|
|
assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
|
|
assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
|
|
assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
|
|
assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
|
|
assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
|
|
assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
|
|
assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
|
|
let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
|
|
let buffer_2 = cx.new_model(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
|
|
// Create an insertion id in buffer 1 that doesn't exist in buffer 2.
|
|
// Add an excerpt from buffer 1 that spans this new insertion.
|
|
buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
|
|
let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer
|
|
.push_excerpts(
|
|
buffer_1.clone(),
|
|
[ExcerptRange {
|
|
context: 0..7,
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
)
|
|
.pop()
|
|
.unwrap()
|
|
});
|
|
|
|
let snapshot_1 = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot_1.text(), "abcd123");
|
|
|
|
// Replace the buffer 1 excerpt with new excerpts from buffer 2.
|
|
let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer.remove_excerpts([excerpt_id_1], cx);
|
|
let mut ids = multibuffer
|
|
.push_excerpts(
|
|
buffer_2.clone(),
|
|
[
|
|
ExcerptRange {
|
|
context: 0..4,
|
|
primary: None,
|
|
},
|
|
ExcerptRange {
|
|
context: 6..10,
|
|
primary: None,
|
|
},
|
|
ExcerptRange {
|
|
context: 12..16,
|
|
primary: None,
|
|
},
|
|
],
|
|
cx,
|
|
)
|
|
.into_iter();
|
|
(ids.next().unwrap(), ids.next().unwrap())
|
|
});
|
|
let snapshot_2 = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
|
|
|
|
// The old excerpt id doesn't get reused.
|
|
assert_ne!(excerpt_id_2, excerpt_id_1);
|
|
|
|
// Resolve some anchors from the previous snapshot in the new snapshot.
|
|
// The current excerpts are from a different buffer, so we don't attempt to
|
|
// resolve the old text anchor in the new buffer.
|
|
assert_eq!(
|
|
snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
snapshot_2.summaries_for_anchors::<usize, _>(&[
|
|
snapshot_1.anchor_before(2),
|
|
snapshot_1.anchor_after(3)
|
|
]),
|
|
vec![0, 0]
|
|
);
|
|
|
|
// Refresh anchors from the old snapshot. The return value indicates that both
|
|
// anchors lost their original excerpt.
|
|
let refresh =
|
|
snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
|
|
assert_eq!(
|
|
refresh,
|
|
&[
|
|
(0, snapshot_2.anchor_before(0), false),
|
|
(1, snapshot_2.anchor_after(0), false),
|
|
]
|
|
);
|
|
|
|
// Replace the middle excerpt with a smaller excerpt in buffer 2,
|
|
// that intersects the old excerpt.
|
|
let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer.remove_excerpts([excerpt_id_3], cx);
|
|
multibuffer
|
|
.insert_excerpts_after(
|
|
excerpt_id_2,
|
|
buffer_2.clone(),
|
|
[ExcerptRange {
|
|
context: 5..8,
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
)
|
|
.pop()
|
|
.unwrap()
|
|
});
|
|
|
|
let snapshot_3 = multibuffer.read(cx).snapshot(cx);
|
|
assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
|
|
assert_ne!(excerpt_id_5, excerpt_id_3);
|
|
|
|
// Resolve some anchors from the previous snapshot in the new snapshot.
|
|
// The third anchor can't be resolved, since its excerpt has been removed,
|
|
// so it resolves to the same position as its predecessor.
|
|
let anchors = [
|
|
snapshot_2.anchor_before(0),
|
|
snapshot_2.anchor_after(2),
|
|
snapshot_2.anchor_after(6),
|
|
snapshot_2.anchor_after(14),
|
|
];
|
|
assert_eq!(
|
|
snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
|
|
&[0, 2, 9, 13]
|
|
);
|
|
|
|
let new_anchors = snapshot_3.refresh_anchors(&anchors);
|
|
assert_eq!(
|
|
new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
|
|
&[(0, true), (1, true), (2, true), (3, true)]
|
|
);
|
|
assert_eq!(
|
|
snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
|
|
&[0, 2, 7, 13]
|
|
);
|
|
}
|
|
|
|
#[gpui::test(iterations = 100)]
|
|
fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
|
|
let operations = env::var("OPERATIONS")
|
|
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
|
.unwrap_or(10);
|
|
|
|
let mut buffers: Vec<Model<Buffer>> = Vec::new();
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
let mut excerpt_ids = Vec::<ExcerptId>::new();
|
|
let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
|
|
let mut anchors = Vec::new();
|
|
let mut old_versions = Vec::new();
|
|
|
|
for _ in 0..operations {
|
|
match rng.gen_range(0..100) {
|
|
0..=14 if !buffers.is_empty() => {
|
|
let buffer = buffers.choose(&mut rng).unwrap();
|
|
buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
|
|
}
|
|
15..=19 if !expected_excerpts.is_empty() => {
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
let ids = multibuffer.excerpt_ids();
|
|
let mut excerpts = HashSet::default();
|
|
for _ in 0..rng.gen_range(0..ids.len()) {
|
|
excerpts.extend(ids.choose(&mut rng).copied());
|
|
}
|
|
|
|
let line_count = rng.gen_range(0..5);
|
|
|
|
let excerpt_ixs = excerpts
|
|
.iter()
|
|
.map(|id| excerpt_ids.iter().position(|i| i == id).unwrap())
|
|
.collect::<Vec<_>>();
|
|
log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
|
|
multibuffer.expand_excerpts(
|
|
excerpts.iter().cloned(),
|
|
line_count,
|
|
ExpandExcerptDirection::UpAndDown,
|
|
cx,
|
|
);
|
|
|
|
if line_count > 0 {
|
|
for id in excerpts {
|
|
let excerpt_ix = excerpt_ids.iter().position(|&i| i == id).unwrap();
|
|
let (buffer, range) = &mut expected_excerpts[excerpt_ix];
|
|
let snapshot = buffer.read(cx).snapshot();
|
|
let mut point_range = range.to_point(&snapshot);
|
|
point_range.start =
|
|
Point::new(point_range.start.row.saturating_sub(line_count), 0);
|
|
point_range.end = snapshot.clip_point(
|
|
Point::new(point_range.end.row + line_count, 0),
|
|
Bias::Left,
|
|
);
|
|
point_range.end.column = snapshot.line_len(point_range.end.row);
|
|
*range = snapshot.anchor_before(point_range.start)
|
|
..snapshot.anchor_after(point_range.end);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
20..=29 if !expected_excerpts.is_empty() => {
|
|
let mut ids_to_remove = vec![];
|
|
for _ in 0..rng.gen_range(1..=3) {
|
|
if expected_excerpts.is_empty() {
|
|
break;
|
|
}
|
|
|
|
let ix = rng.gen_range(0..expected_excerpts.len());
|
|
ids_to_remove.push(excerpt_ids.remove(ix));
|
|
let (buffer, range) = expected_excerpts.remove(ix);
|
|
let buffer = buffer.read(cx);
|
|
log::info!(
|
|
"Removing excerpt {}: {:?}",
|
|
ix,
|
|
buffer
|
|
.text_for_range(range.to_offset(buffer))
|
|
.collect::<String>(),
|
|
);
|
|
}
|
|
let snapshot = multibuffer.read(cx).read(cx);
|
|
ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
|
|
drop(snapshot);
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer.remove_excerpts(ids_to_remove, cx)
|
|
});
|
|
}
|
|
30..=39 if !expected_excerpts.is_empty() => {
|
|
let multibuffer = multibuffer.read(cx).read(cx);
|
|
let offset =
|
|
multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
|
|
let bias = if rng.gen() { Bias::Left } else { Bias::Right };
|
|
log::info!("Creating anchor at {} with bias {:?}", offset, bias);
|
|
anchors.push(multibuffer.anchor_at(offset, bias));
|
|
anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
|
|
}
|
|
40..=44 if !anchors.is_empty() => {
|
|
let multibuffer = multibuffer.read(cx).read(cx);
|
|
let prev_len = anchors.len();
|
|
anchors = multibuffer
|
|
.refresh_anchors(&anchors)
|
|
.into_iter()
|
|
.map(|a| a.1)
|
|
.collect();
|
|
|
|
// Ensure the newly-refreshed anchors point to a valid excerpt and don't
|
|
// overshoot its boundaries.
|
|
assert_eq!(anchors.len(), prev_len);
|
|
for anchor in &anchors {
|
|
if anchor.excerpt_id == ExcerptId::min()
|
|
|| anchor.excerpt_id == ExcerptId::max()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
|
|
assert_eq!(excerpt.id, anchor.excerpt_id);
|
|
assert!(excerpt.contains(anchor));
|
|
}
|
|
}
|
|
_ => {
|
|
let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
|
|
let base_text = util::RandomCharIter::new(&mut rng)
|
|
.take(25)
|
|
.collect::<String>();
|
|
|
|
buffers.push(cx.new_model(|cx| Buffer::local(base_text, cx)));
|
|
buffers.last().unwrap()
|
|
} else {
|
|
buffers.choose(&mut rng).unwrap()
|
|
};
|
|
|
|
let buffer = buffer_handle.read(cx);
|
|
let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
|
|
let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
|
|
let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
|
|
let prev_excerpt_ix = rng.gen_range(0..=expected_excerpts.len());
|
|
let prev_excerpt_id = excerpt_ids
|
|
.get(prev_excerpt_ix)
|
|
.cloned()
|
|
.unwrap_or_else(ExcerptId::max);
|
|
let excerpt_ix = (prev_excerpt_ix + 1).min(expected_excerpts.len());
|
|
|
|
log::info!(
|
|
"Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
|
|
excerpt_ix,
|
|
expected_excerpts.len(),
|
|
buffer_handle.read(cx).remote_id(),
|
|
buffer.text(),
|
|
start_ix..end_ix,
|
|
&buffer.text()[start_ix..end_ix]
|
|
);
|
|
|
|
let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
|
|
multibuffer
|
|
.insert_excerpts_after(
|
|
prev_excerpt_id,
|
|
buffer_handle.clone(),
|
|
[ExcerptRange {
|
|
context: start_ix..end_ix,
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
)
|
|
.pop()
|
|
.unwrap()
|
|
});
|
|
|
|
excerpt_ids.insert(excerpt_ix, excerpt_id);
|
|
expected_excerpts.insert(excerpt_ix, (buffer_handle.clone(), anchor_range));
|
|
}
|
|
}
|
|
|
|
if rng.gen_bool(0.3) {
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
|
|
})
|
|
}
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
let mut excerpt_starts = Vec::new();
|
|
let mut expected_text = String::new();
|
|
let mut expected_buffer_rows = Vec::new();
|
|
for (buffer, range) in &expected_excerpts {
|
|
let buffer = buffer.read(cx);
|
|
let buffer_range = range.to_offset(buffer);
|
|
|
|
excerpt_starts.push(TextSummary::from(expected_text.as_str()));
|
|
expected_text.extend(buffer.text_for_range(buffer_range.clone()));
|
|
expected_text.push('\n');
|
|
|
|
let buffer_row_range = buffer.offset_to_point(buffer_range.start).row
|
|
..=buffer.offset_to_point(buffer_range.end).row;
|
|
for row in buffer_row_range {
|
|
expected_buffer_rows.push(Some(row));
|
|
}
|
|
}
|
|
// Remove final trailing newline.
|
|
if !expected_excerpts.is_empty() {
|
|
expected_text.pop();
|
|
}
|
|
|
|
// Always report one buffer row
|
|
if expected_buffer_rows.is_empty() {
|
|
expected_buffer_rows.push(Some(0));
|
|
}
|
|
|
|
assert_eq!(snapshot.text(), expected_text);
|
|
log::info!("MultiBuffer text: {:?}", expected_text);
|
|
|
|
assert_eq!(
|
|
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
|
|
expected_buffer_rows,
|
|
);
|
|
|
|
for _ in 0..5 {
|
|
let start_row = rng.gen_range(0..=expected_buffer_rows.len());
|
|
assert_eq!(
|
|
snapshot
|
|
.buffer_rows(MultiBufferRow(start_row as u32))
|
|
.collect::<Vec<_>>(),
|
|
&expected_buffer_rows[start_row..],
|
|
"buffer_rows({})",
|
|
start_row
|
|
);
|
|
}
|
|
|
|
assert_eq!(
|
|
snapshot.widest_line_number(),
|
|
expected_buffer_rows.into_iter().flatten().max().unwrap() + 1
|
|
);
|
|
|
|
let mut excerpt_starts = excerpt_starts.into_iter();
|
|
for (buffer, range) in &expected_excerpts {
|
|
let buffer = buffer.read(cx);
|
|
let buffer_id = buffer.remote_id();
|
|
let buffer_range = range.to_offset(buffer);
|
|
let buffer_start_point = buffer.offset_to_point(buffer_range.start);
|
|
let buffer_start_point_utf16 =
|
|
buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
|
|
|
|
let excerpt_start = excerpt_starts.next().unwrap();
|
|
let mut offset = excerpt_start.len;
|
|
let mut buffer_offset = buffer_range.start;
|
|
let mut point = excerpt_start.lines;
|
|
let mut buffer_point = buffer_start_point;
|
|
let mut point_utf16 = excerpt_start.lines_utf16();
|
|
let mut buffer_point_utf16 = buffer_start_point_utf16;
|
|
for ch in buffer
|
|
.snapshot()
|
|
.chunks(buffer_range.clone(), false)
|
|
.flat_map(|c| c.text.chars())
|
|
{
|
|
for _ in 0..ch.len_utf8() {
|
|
let left_offset = snapshot.clip_offset(offset, Bias::Left);
|
|
let right_offset = snapshot.clip_offset(offset, Bias::Right);
|
|
let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
|
|
let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
|
|
assert_eq!(
|
|
left_offset,
|
|
excerpt_start.len + (buffer_left_offset - buffer_range.start),
|
|
"clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
|
|
offset,
|
|
buffer_id,
|
|
buffer_offset,
|
|
);
|
|
assert_eq!(
|
|
right_offset,
|
|
excerpt_start.len + (buffer_right_offset - buffer_range.start),
|
|
"clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
|
|
offset,
|
|
buffer_id,
|
|
buffer_offset,
|
|
);
|
|
|
|
let left_point = snapshot.clip_point(point, Bias::Left);
|
|
let right_point = snapshot.clip_point(point, Bias::Right);
|
|
let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
|
|
let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
|
|
assert_eq!(
|
|
left_point,
|
|
excerpt_start.lines + (buffer_left_point - buffer_start_point),
|
|
"clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
|
|
point,
|
|
buffer_id,
|
|
buffer_point,
|
|
);
|
|
assert_eq!(
|
|
right_point,
|
|
excerpt_start.lines + (buffer_right_point - buffer_start_point),
|
|
"clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
|
|
point,
|
|
buffer_id,
|
|
buffer_point,
|
|
);
|
|
|
|
assert_eq!(
|
|
snapshot.point_to_offset(left_point),
|
|
left_offset,
|
|
"point_to_offset({:?})",
|
|
left_point,
|
|
);
|
|
assert_eq!(
|
|
snapshot.offset_to_point(left_offset),
|
|
left_point,
|
|
"offset_to_point({:?})",
|
|
left_offset,
|
|
);
|
|
|
|
offset += 1;
|
|
buffer_offset += 1;
|
|
if ch == '\n' {
|
|
point += Point::new(1, 0);
|
|
buffer_point += Point::new(1, 0);
|
|
} else {
|
|
point += Point::new(0, 1);
|
|
buffer_point += Point::new(0, 1);
|
|
}
|
|
}
|
|
|
|
for _ in 0..ch.len_utf16() {
|
|
let left_point_utf16 =
|
|
snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left);
|
|
let right_point_utf16 =
|
|
snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right);
|
|
let buffer_left_point_utf16 =
|
|
buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left);
|
|
let buffer_right_point_utf16 =
|
|
buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right);
|
|
assert_eq!(
|
|
left_point_utf16,
|
|
excerpt_start.lines_utf16()
|
|
+ (buffer_left_point_utf16 - buffer_start_point_utf16),
|
|
"clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
|
|
point_utf16,
|
|
buffer_id,
|
|
buffer_point_utf16,
|
|
);
|
|
assert_eq!(
|
|
right_point_utf16,
|
|
excerpt_start.lines_utf16()
|
|
+ (buffer_right_point_utf16 - buffer_start_point_utf16),
|
|
"clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
|
|
point_utf16,
|
|
buffer_id,
|
|
buffer_point_utf16,
|
|
);
|
|
|
|
if ch == '\n' {
|
|
point_utf16 += PointUtf16::new(1, 0);
|
|
buffer_point_utf16 += PointUtf16::new(1, 0);
|
|
} else {
|
|
point_utf16 += PointUtf16::new(0, 1);
|
|
buffer_point_utf16 += PointUtf16::new(0, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (row, line) in expected_text.split('\n').enumerate() {
|
|
assert_eq!(
|
|
snapshot.line_len(MultiBufferRow(row as u32)),
|
|
line.len() as u32,
|
|
"line_len({}).",
|
|
row
|
|
);
|
|
}
|
|
|
|
let text_rope = Rope::from(expected_text.as_str());
|
|
for _ in 0..10 {
|
|
let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
|
|
let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
|
|
|
|
let text_for_range = snapshot
|
|
.text_for_range(start_ix..end_ix)
|
|
.collect::<String>();
|
|
assert_eq!(
|
|
text_for_range,
|
|
&expected_text[start_ix..end_ix],
|
|
"incorrect text for range {:?}",
|
|
start_ix..end_ix
|
|
);
|
|
|
|
let excerpted_buffer_ranges = multibuffer
|
|
.read(cx)
|
|
.range_to_buffer_ranges(start_ix..end_ix, cx);
|
|
let excerpted_buffers_text = excerpted_buffer_ranges
|
|
.iter()
|
|
.map(|(buffer, buffer_range, _)| {
|
|
buffer
|
|
.read(cx)
|
|
.text_for_range(buffer_range.clone())
|
|
.collect::<String>()
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
assert_eq!(excerpted_buffers_text, text_for_range);
|
|
if !expected_excerpts.is_empty() {
|
|
assert!(!excerpted_buffer_ranges.is_empty());
|
|
}
|
|
|
|
let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
|
|
assert_eq!(
|
|
snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
|
|
expected_summary,
|
|
"incorrect summary for range {:?}",
|
|
start_ix..end_ix
|
|
);
|
|
}
|
|
|
|
// Anchor resolution
|
|
let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
|
|
assert_eq!(anchors.len(), summaries.len());
|
|
for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
|
|
assert!(resolved_offset <= snapshot.len());
|
|
assert_eq!(
|
|
snapshot.summary_for_anchor::<usize>(anchor),
|
|
resolved_offset
|
|
);
|
|
}
|
|
|
|
for _ in 0..10 {
|
|
let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
|
|
assert_eq!(
|
|
snapshot.reversed_chars_at(end_ix).collect::<String>(),
|
|
expected_text[..end_ix].chars().rev().collect::<String>(),
|
|
);
|
|
}
|
|
|
|
for _ in 0..10 {
|
|
let end_ix = rng.gen_range(0..=text_rope.len());
|
|
let start_ix = rng.gen_range(0..=end_ix);
|
|
assert_eq!(
|
|
snapshot
|
|
.bytes_in_range(start_ix..end_ix)
|
|
.flatten()
|
|
.copied()
|
|
.collect::<Vec<_>>(),
|
|
expected_text.as_bytes()[start_ix..end_ix].to_vec(),
|
|
"bytes_in_range({:?})",
|
|
start_ix..end_ix,
|
|
);
|
|
}
|
|
}
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
for (old_snapshot, subscription) in old_versions {
|
|
let edits = subscription.consume().into_inner();
|
|
|
|
log::info!(
|
|
"applying subscription edits to old text: {:?}: {:?}",
|
|
old_snapshot.text(),
|
|
edits,
|
|
);
|
|
|
|
let mut text = old_snapshot.text();
|
|
for edit in edits {
|
|
let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
|
|
text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
|
|
}
|
|
assert_eq!(text.to_string(), snapshot.text());
|
|
}
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_history(cx: &mut AppContext) {
|
|
let test_settings = SettingsStore::test(cx);
|
|
cx.set_global(test_settings);
|
|
let group_interval: Duration = Duration::from_millis(1);
|
|
let buffer_1 = cx.new_model(|cx| {
|
|
let mut buf = Buffer::local("1234", cx);
|
|
buf.set_group_interval(group_interval);
|
|
buf
|
|
});
|
|
let buffer_2 = cx.new_model(|cx| {
|
|
let mut buf = Buffer::local("5678", cx);
|
|
buf.set_group_interval(group_interval);
|
|
buf
|
|
});
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
|
|
multibuffer.update(cx, |this, _| {
|
|
this.history.group_interval = group_interval;
|
|
});
|
|
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 mut now = Instant::now();
|
|
|
|
multibuffer.update(cx, |multibuffer, cx| {
|
|
let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
|
|
multibuffer.edit(
|
|
[
|
|
(Point::new(0, 0)..Point::new(0, 0), "A"),
|
|
(Point::new(1, 0)..Point::new(1, 0), "A"),
|
|
],
|
|
None,
|
|
cx,
|
|
);
|
|
multibuffer.edit(
|
|
[
|
|
(Point::new(0, 1)..Point::new(0, 1), "B"),
|
|
(Point::new(1, 1)..Point::new(1, 1), "B"),
|
|
],
|
|
None,
|
|
cx,
|
|
);
|
|
multibuffer.end_transaction_at(now, cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
|
|
|
// Verify edited ranges for transaction 1
|
|
assert_eq!(
|
|
multibuffer.edited_ranges_for_transaction(transaction_1, cx),
|
|
&[
|
|
Point::new(0, 0)..Point::new(0, 2),
|
|
Point::new(1, 0)..Point::new(1, 2)
|
|
]
|
|
);
|
|
|
|
// Edit buffer 1 through the multibuffer
|
|
now += 2 * group_interval;
|
|
multibuffer.start_transaction_at(now, cx);
|
|
multibuffer.edit([(2..2, "C")], None, cx);
|
|
multibuffer.end_transaction_at(now, cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
|
|
|
|
// Edit buffer 1 independently
|
|
buffer_1.update(cx, |buffer_1, cx| {
|
|
buffer_1.start_transaction_at(now);
|
|
buffer_1.edit([(3..3, "D")], None, cx);
|
|
buffer_1.end_transaction_at(now, cx);
|
|
|
|
now += 2 * group_interval;
|
|
buffer_1.start_transaction_at(now);
|
|
buffer_1.edit([(4..4, "E")], None, cx);
|
|
buffer_1.end_transaction_at(now, cx);
|
|
});
|
|
assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
|
|
|
|
// An undo in the multibuffer undoes the multibuffer transaction
|
|
// and also any individual buffer edits that have occurred since
|
|
// that transaction.
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
|
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
|
|
|
multibuffer.redo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
|
|
|
multibuffer.redo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
|
|
|
|
// Undo buffer 2 independently.
|
|
buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
|
|
assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
|
|
|
|
// An undo in the multibuffer undoes the components of the
|
|
// the last multibuffer transaction that are not already undone.
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
|
|
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
|
|
|
multibuffer.redo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
|
|
|
buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
|
|
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
|
|
|
// Redo stack gets cleared after an edit.
|
|
now += 2 * group_interval;
|
|
multibuffer.start_transaction_at(now, cx);
|
|
multibuffer.edit([(0..0, "X")], None, cx);
|
|
multibuffer.end_transaction_at(now, cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
|
multibuffer.redo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
|
|
|
// Transactions can be grouped manually.
|
|
multibuffer.redo(cx);
|
|
multibuffer.redo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
|
multibuffer.group_until_transaction(transaction_1, cx);
|
|
multibuffer.undo(cx);
|
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
|
multibuffer.redo(cx);
|
|
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(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: &[(ExcerptId, BufferId, Range<Anchor>)],
|
|
expected: &Vec<(ExcerptId, BufferId, Range<Anchor>)>,
|
|
) {
|
|
assert_eq!(actual.len(), expected.len());
|
|
|
|
actual
|
|
.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(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(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(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(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(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);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_split_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(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.read(cx).snapshot(cx);
|
|
|
|
let buffer_1_len = buffer_1.read(cx).len();
|
|
let buffer_2_len = buffer_2.read(cx).len();
|
|
let buffer_1_midpoint = buffer_1_len / 2;
|
|
let buffer_2_start = buffer_1_len + '\n'.len_utf8();
|
|
let buffer_2_midpoint = buffer_2_start + buffer_2_len / 2;
|
|
let total_len = buffer_2_start + buffer_2_len;
|
|
|
|
let input_ranges = [
|
|
0..buffer_1_midpoint,
|
|
buffer_1_midpoint..buffer_2_midpoint,
|
|
buffer_2_midpoint..total_len,
|
|
]
|
|
.map(|range| snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end));
|
|
|
|
let actual_ranges = snapshot
|
|
.split_ranges(input_ranges.into_iter())
|
|
.map(|range| range.to_offset(&snapshot))
|
|
.collect::<Vec<_>>();
|
|
|
|
let expected_ranges = vec![
|
|
0..buffer_1_midpoint,
|
|
buffer_1_midpoint..buffer_1_len,
|
|
buffer_2_start..buffer_2_midpoint,
|
|
buffer_2_midpoint..total_len,
|
|
];
|
|
|
|
assert_eq!(actual_ranges, expected_ranges);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn test_split_ranges_single_range_spanning_three_excerpts(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, 'm'), cx));
|
|
let multibuffer = cx.new_model(|_| MultiBuffer::new(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,
|
|
);
|
|
multibuffer.push_excerpts(
|
|
buffer_3.clone(),
|
|
[ExcerptRange {
|
|
context: 0..buffer_3.read(cx).len(),
|
|
primary: None,
|
|
}],
|
|
cx,
|
|
);
|
|
});
|
|
|
|
let snapshot = multibuffer.read(cx).snapshot(cx);
|
|
|
|
let buffer_1_len = buffer_1.read(cx).len();
|
|
let buffer_2_len = buffer_2.read(cx).len();
|
|
let buffer_3_len = buffer_3.read(cx).len();
|
|
let buffer_2_start = buffer_1_len + '\n'.len_utf8();
|
|
let buffer_3_start = buffer_2_start + buffer_2_len + '\n'.len_utf8();
|
|
let buffer_1_midpoint = buffer_1_len / 2;
|
|
let buffer_3_midpoint = buffer_3_start + buffer_3_len / 2;
|
|
|
|
let input_range =
|
|
snapshot.anchor_before(buffer_1_midpoint)..snapshot.anchor_after(buffer_3_midpoint);
|
|
|
|
let actual_ranges = snapshot
|
|
.split_ranges(std::iter::once(input_range))
|
|
.map(|range| range.to_offset(&snapshot))
|
|
.collect::<Vec<_>>();
|
|
|
|
let expected_ranges = vec![
|
|
buffer_1_midpoint..buffer_1_len,
|
|
buffer_2_start..buffer_2_start + buffer_2_len,
|
|
buffer_3_start..buffer_3_midpoint,
|
|
];
|
|
|
|
assert_eq!(actual_ranges, expected_ranges);
|
|
}
|