Emit events from a multibuffer when adding/removing excerpts

This commit is contained in:
Max Brunsfeld 2022-11-28 15:47:44 -08:00
parent 3adc0b947f
commit 3eac3e20d5
2 changed files with 175 additions and 31 deletions

View file

@ -9,9 +9,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
pub use language::Completion;
use language::{
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline,
OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
DiagnosticEntry, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem,
Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _,
ToPointUtf16 as _, TransactionId, Unclipped,
};
use smallvec::SmallVec;
use std::{
@ -833,6 +833,30 @@ impl MultiBuffer {
) -> Vec<ExcerptId>
where
O: text::ToOffset,
{
let mut ids = Vec::new();
let mut next_excerpt_id = self.next_excerpt_id;
self.insert_excerpts_with_ids_after(
prev_excerpt_id,
buffer,
ranges.into_iter().map(|range| {
let id = ExcerptId(post_inc(&mut next_excerpt_id));
ids.push(id);
(id, range)
}),
cx,
);
ids
}
pub fn insert_excerpts_with_ids_after<O>(
&mut self,
prev_excerpt_id: ExcerptId,
buffer: ModelHandle<Buffer>,
ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
cx: &mut ModelContext<Self>,
) where
O: text::ToOffset,
{
assert_eq!(self.history.transaction_depth, 0);
let mut ranges = ranges.into_iter().peekable();
@ -858,7 +882,7 @@ impl MultiBuffer {
cx.observe(&buffer, |_, _, cx| cx.notify()),
cx.subscribe(&buffer, Self::on_buffer_event),
],
buffer,
buffer: buffer.clone(),
});
let mut snapshot = self.snapshot.borrow_mut();
@ -883,8 +907,8 @@ impl MultiBuffer {
Locator::max()
};
let mut ids = Vec::new();
while let Some(range) = ranges.next() {
let mut excerpts = Vec::new();
while let Some((id, range)) = ranges.next() {
let locator = Locator::between(&prev_locator, &next_locator);
if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
buffer_state.excerpts.insert(ix, locator.clone());
@ -897,7 +921,10 @@ impl MultiBuffer {
..buffer_snapshot.anchor_after(&primary.end)
}),
};
let id = ExcerptId(post_inc(&mut self.next_excerpt_id));
if id.0 >= self.next_excerpt_id {
self.next_excerpt_id = id.0 + 1;
}
excerpts.push((id, range.clone()));
let excerpt = Excerpt::new(
id,
locator.clone(),
@ -909,7 +936,6 @@ impl MultiBuffer {
new_excerpts.push(excerpt, &());
prev_locator = locator.clone();
new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &());
ids.push(id);
}
let edit_end = new_excerpts.summary().text.len;
@ -929,12 +955,17 @@ impl MultiBuffer {
new: edit_start..edit_end,
}]);
cx.emit(Event::Edited);
cx.emit(Event::ExcerptsAdded {
buffer,
predecessor: prev_excerpt_id,
excerpts,
});
cx.notify();
ids
}
pub fn clear(&mut self, cx: &mut ModelContext<Self>) {
self.sync(cx);
let ids = self.excerpt_ids();
self.buffers.borrow_mut().clear();
let mut snapshot = self.snapshot.borrow_mut();
let prev_len = snapshot.len();
@ -948,6 +979,7 @@ impl MultiBuffer {
new: 0..0,
}]);
cx.emit(Event::Edited);
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
}
@ -1071,12 +1103,14 @@ impl MultiBuffer {
cx: &mut ModelContext<Self>,
) {
self.sync(cx);
let ids = excerpt_ids.into_iter().collect::<Vec<_>>();
let mut buffers = self.buffers.borrow_mut();
let mut snapshot = self.snapshot.borrow_mut();
let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut edits = Vec::new();
let mut excerpt_ids = excerpt_ids.into_iter().peekable();
let mut excerpt_ids = ids.iter().copied().peekable();
while let Some(excerpt_id) = excerpt_ids.next() {
// Seek to the next excerpt to remove, preserving any preceding excerpts.
@ -1143,6 +1177,7 @@ impl MultiBuffer {
self.subscriptions.publish_mut(edits);
cx.emit(Event::Edited);
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
}
@ -1165,10 +1200,22 @@ impl MultiBuffer {
fn on_buffer_event(
&mut self,
_: ModelHandle<Buffer>,
event: &Event,
event: &language::Event,
cx: &mut ModelContext<Self>,
) {
cx.emit(event.clone());
cx.emit(match event {
language::Event::Edited => Event::Edited,
language::Event::DirtyChanged => Event::DirtyChanged,
language::Event::Saved => Event::Saved,
language::Event::FileHandleChanged => Event::FileHandleChanged,
language::Event::Reloaded => Event::Reloaded,
language::Event::Reparsed => Event::Reparsed,
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
language::Event::Closed => Event::Closed,
//
language::Event::Operation(_) => return,
});
}
pub fn all_buffers(&self) -> HashSet<ModelHandle<Buffer>> {
@ -1603,8 +1650,28 @@ impl MultiBuffer {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
ExcerptsAdded {
buffer: ModelHandle<Buffer>,
predecessor: ExcerptId,
excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
},
ExcerptsRemoved {
ids: Vec<ExcerptId>,
},
Edited,
Reloaded,
Reparsed,
Saved,
FileHandleChanged,
Closed,
DirtyChanged,
DiagnosticsUpdated,
}
impl Entity for MultiBuffer {
type Event = language::Event;
type Event = Event;
}
impl MultiBufferSnapshot {
@ -3468,7 +3535,7 @@ mod tests {
use util::test::sample_text;
#[gpui::test]
fn test_singleton_multibuffer(cx: &mut MutableAppContext) {
fn test_singleton(cx: &mut MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
@ -3495,7 +3562,7 @@ mod tests {
}
#[gpui::test]
fn test_remote_multibuffer(cx: &mut MutableAppContext) {
fn test_remote(cx: &mut MutableAppContext) {
let host_buffer = cx.add_model(|cx| Buffer::new(0, "a", cx));
let guest_buffer = cx.add_model(|cx| {
let state = host_buffer.read(cx).to_proto();
@ -3526,7 +3593,7 @@ mod tests {
}
#[gpui::test]
fn test_excerpt_buffer(cx: &mut MutableAppContext) {
fn test_excerpt_boundaries_and_clipping(cx: &mut MutableAppContext) {
let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
@ -3535,7 +3602,9 @@ mod tests {
multibuffer.update(cx, |_, cx| {
let events = events.clone();
cx.subscribe(&multibuffer, move |_, _, event, _| {
events.borrow_mut().push(event.clone())
if let Event::Edited = event {
events.borrow_mut().push(event.clone())
}
})
.detach();
});
@ -3748,7 +3817,84 @@ mod tests {
}
#[gpui::test]
fn test_excerpts_with_context_lines(cx: &mut MutableAppContext) {
fn test_excerpt_events(cx: &mut MutableAppContext) {
let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(10, 3, 'a'), cx));
let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(10, 3, 'm'), cx));
let leader_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
let follower_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
follower_multibuffer.update(cx, |_, cx| {
cx.subscribe(&leader_multibuffer, |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),
_ => {}
}
})
.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(),
);
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(),
);
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(),
);
}
#[gpui::test]
fn test_push_excerpts_with_context_lines(cx: &mut MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(20, 3, 'a'), cx));
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
@ -3784,7 +3930,7 @@ mod tests {
}
#[gpui::test]
fn test_empty_excerpt_buffer(cx: &mut MutableAppContext) {
fn test_empty_multibuffer(cx: &mut MutableAppContext) {
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
let snapshot = multibuffer.read(cx).snapshot(cx);
@ -3872,9 +4018,7 @@ mod tests {
}
#[gpui::test]
fn test_multibuffer_resolving_anchors_after_replacing_their_excerpts(
cx: &mut MutableAppContext,
) {
fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut MutableAppContext) {
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "ABCDEFGHIJKLMNOP", cx));
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));