diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5bbeed3fb56dd754aa181f867a85956ba71d90b4..4a7a7893a1933fc81e442228e2b92e236aeaeaa6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6368,22 +6368,22 @@ impl Editor { fn on_buffer_event( &mut self, _: ModelHandle, - event: &language::Event, + event: &multi_buffer::Event, cx: &mut ViewContext, ) { match event { - language::Event::Edited => { + multi_buffer::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); cx.emit(Event::BufferEdited); } - language::Event::Reparsed => cx.emit(Event::Reparsed), - language::Event::DirtyChanged => cx.emit(Event::DirtyChanged), - language::Event::Saved => cx.emit(Event::Saved), - language::Event::FileHandleChanged => cx.emit(Event::TitleChanged), - language::Event::Reloaded => cx.emit(Event::TitleChanged), - language::Event::Closed => cx.emit(Event::Closed), - language::Event::DiagnosticsUpdated => { + multi_buffer::Event::Reparsed => cx.emit(Event::Reparsed), + multi_buffer::Event::DirtyChanged => cx.emit(Event::DirtyChanged), + multi_buffer::Event::Saved => cx.emit(Event::Saved), + multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged), + multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged), + multi_buffer::Event::Closed => cx.emit(Event::Closed), + multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); } _ => {} diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index d758792e6c29f88e1b17a344c949d2c50f999454..ace07a347fb1241327955dc757333e824571d428 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -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 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( + &mut self, + prev_excerpt_id: ExcerptId, + buffer: ModelHandle, + ranges: impl IntoIterator)>, + cx: &mut ModelContext, + ) 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.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.sync(cx); + let ids = excerpt_ids.into_iter().collect::>(); + 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, - event: &Event, + event: &language::Event, cx: &mut ModelContext, ) { - 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> { @@ -1603,8 +1650,28 @@ impl MultiBuffer { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + ExcerptsAdded { + buffer: ModelHandle, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, + 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));