From be1ac78e110a28fb9b4464aeeea74d3bb362870b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Wed, 26 Feb 2025 17:39:33 -0300 Subject: [PATCH] Unfold buffers in multibuffers when editing them (#25677) Release Notes: - Multibuffers: Unfold excerpts when editing their contents. Co-authored-by: Max Brunsfeld --- crates/editor/src/display_map.rs | 16 ++++-- crates/editor/src/display_map/block_map.rs | 61 ++++++++++++++-------- crates/editor/src/editor.rs | 19 +++++-- crates/editor/src/editor_tests.rs | 32 ++++++++---- crates/multi_buffer/src/multi_buffer.rs | 13 +++-- 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a37b4abe38..59a852f1d9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -330,7 +330,11 @@ impl DisplayMap { block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive); } - pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context) { + pub fn fold_buffers( + &mut self, + buffer_ids: impl IntoIterator, + cx: &mut Context, + ) { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); @@ -341,10 +345,14 @@ impl DisplayMap { .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); let mut block_map = self.block_map.write(snapshot, edits); - block_map.fold_buffer(buffer_id, self.buffer.read(cx), cx) + block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx) } - pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context) { + pub fn unfold_buffers( + &mut self, + buffer_ids: impl IntoIterator, + cx: &mut Context, + ) { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); @@ -355,7 +363,7 @@ impl DisplayMap { .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); let mut block_map = self.block_map.write(snapshot, edits); - block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx) + block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx) } pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 3a8de4fcc7..b0a9810d50 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1239,26 +1239,45 @@ impl<'a> BlockMapWriter<'a> { self.remove(blocks_to_remove); } - pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) { - self.0.folded_buffers.insert(buffer_id); - self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx); - } - - pub fn unfold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) { - self.0.folded_buffers.remove(&buffer_id); - self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx); - } - - fn recompute_blocks_for_buffer( + pub fn fold_buffers( &mut self, - buffer_id: BufferId, + buffer_ids: impl IntoIterator, multi_buffer: &MultiBuffer, cx: &App, ) { - let wrap_snapshot = self.0.wrap_snapshot.borrow().clone(); + self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx); + } + + pub fn unfold_buffers( + &mut self, + buffer_ids: impl IntoIterator, + multi_buffer: &MultiBuffer, + cx: &App, + ) { + self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx); + } + + fn fold_or_unfold_buffers( + &mut self, + fold: bool, + buffer_ids: impl IntoIterator, + multi_buffer: &MultiBuffer, + cx: &App, + ) { + let mut ranges = Vec::new(); + for buffer_id in buffer_ids { + if fold { + self.0.folded_buffers.insert(buffer_id); + } else { + self.0.folded_buffers.remove(&buffer_id); + } + ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx)); + } + ranges.sort_unstable_by_key(|range| range.start); let mut edits = Patch::default(); - for range in multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx) { + let wrap_snapshot = self.0.wrap_snapshot.borrow().clone(); + for range in ranges { let last_edit_row = cmp::min( wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1, wrap_snapshot.max_point().row(), @@ -2730,7 +2749,7 @@ mod tests { let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); buffer.read_with(cx, |buffer, cx| { - writer.fold_buffer(buffer_id_1, buffer, cx); + writer.fold_buffers([buffer_id_1], buffer, cx); }); let excerpt_blocks_1 = writer.insert(vec![BlockProperties { style: BlockStyle::Fixed, @@ -2805,7 +2824,7 @@ mod tests { let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); buffer.read_with(cx, |buffer, cx| { - writer.fold_buffer(buffer_id_2, buffer, cx); + writer.fold_buffers([buffer_id_2], buffer, cx); }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot @@ -2861,7 +2880,7 @@ mod tests { let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); buffer.read_with(cx, |buffer, cx| { - writer.unfold_buffer(buffer_id_1, buffer, cx); + writer.unfold_buffers([buffer_id_1], buffer, cx); }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot @@ -2922,7 +2941,7 @@ mod tests { let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); buffer.read_with(cx, |buffer, cx| { - writer.fold_buffer(buffer_id_3, buffer, cx); + writer.fold_buffers([buffer_id_3], buffer, cx); }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot @@ -3000,7 +3019,7 @@ mod tests { let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); buffer.read_with(cx, |buffer, cx| { - writer.fold_buffer(buffer_id, buffer, cx); + writer.fold_buffers([buffer_id], buffer, cx); }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot @@ -3250,7 +3269,7 @@ mod tests { ); folded_count += 1; unfolded_count -= 1; - block_map.fold_buffer(buffer_to_fold, buffer, cx); + block_map.fold_buffers([buffer_to_fold], buffer, cx); } if unfold { let buffer_to_unfold = @@ -3258,7 +3277,7 @@ mod tests { log::info!("Unfolding {buffer_to_unfold:?}"); unfolded_count += 1; folded_count -= 1; - block_map.unfold_buffer(buffer_to_unfold, buffer, cx); + block_map.unfold_buffers([buffer_to_unfold], buffer, cx); } log::info!( "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}" diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 04d5db9bf3..09cbdcf8be 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13314,8 +13314,9 @@ impl Editor { return; } let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); - self.display_map - .update(cx, |display_map, cx| display_map.fold_buffer(buffer_id, cx)); + self.display_map.update(cx, |display_map, cx| { + display_map.fold_buffers([buffer_id], cx) + }); cx.emit(EditorEvent::BufferFoldToggled { ids: folded_excerpts.iter().map(|&(id, _)| id).collect(), folded: true, @@ -13329,7 +13330,7 @@ impl Editor { } let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); self.display_map.update(cx, |display_map, cx| { - display_map.unfold_buffer(buffer_id, cx); + display_map.unfold_buffers([buffer_id], cx); }); cx.emit(EditorEvent::BufferFoldToggled { ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(), @@ -15212,8 +15213,16 @@ impl Editor { .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some()); cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() }) } - multi_buffer::Event::ExcerptsEdited { ids } => { - cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() }) + multi_buffer::Event::ExcerptsEdited { + excerpt_ids, + buffer_ids, + } => { + self.display_map.update(cx, |map, cx| { + map.unfold_buffers(buffer_ids.iter().copied(), cx) + }); + cx.emit(EditorEvent::ExcerptsEdited { + ids: excerpt_ids.clone(), + }) } multi_buffer::Event::ExcerptsExpanded { ids } => { self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5d2bcbdf52..5f95006fdb 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -15644,7 +15644,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) { } #[gpui::test] -async fn test_multi_buffer_folding(cx: &mut TestAppContext) { +async fn test_folding_buffers(cx: &mut TestAppContext) { init_test(cx, |_| {}); let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); @@ -15751,7 +15751,7 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) { let multi_buffer_editor = cx.new_window_entity(|window, cx| { Editor::new( EditorMode::Full, - multi_buffer, + multi_buffer.clone(), Some(project.clone()), true, window, @@ -15759,10 +15759,9 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) { ) }); - let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n"; assert_eq!( multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), - full_text, + "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n", ); multi_buffer_editor.update(cx, |editor, cx| { @@ -15808,12 +15807,25 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) { "After unfolding the second buffer, its text should be displayed" ); - multi_buffer_editor.update(cx, |editor, cx| { - editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx) + // Typing inside of buffer 1 causes that buffer to be unfolded. + multi_buffer_editor.update_in(cx, |editor, window, cx| { + assert_eq!( + multi_buffer + .read(cx) + .snapshot(cx) + .text_for_range(Point::new(1, 0)..Point::new(1, 4)) + .collect::(), + "bbbb" + ); + editor.change_selections(None, window, cx, |selections| { + selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]); + }); + editor.handle_input("B", window, cx); }); + assert_eq!( multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), - "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n", + "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n", "After unfolding the first buffer, its and 2nd buffer's text should be displayed" ); @@ -15822,13 +15834,13 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) { }); assert_eq!( multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), - full_text, + "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n", "After unfolding the all buffers, all original text should be displayed" ); } #[gpui::test] -async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) { +async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) { init_test(cx, |_| {}); let sample_text_1 = "1111\n2222\n3333".to_string(); @@ -15977,7 +15989,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) { } #[gpui::test] -async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) { +async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) { init_test(cx, |_| {}); let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 120e83224a..3918eacd6b 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -98,7 +98,8 @@ pub enum Event { ids: Vec, }, ExcerptsEdited { - ids: Vec, + excerpt_ids: Vec, + buffer_ids: Vec, }, DiffHunksToggled, Edited { @@ -767,7 +768,9 @@ impl MultiBuffer { this.convert_edits_to_buffer_edits(edits, &snapshot, &original_start_columns); drop(snapshot); + let mut buffer_ids = Vec::new(); for (buffer_id, mut edits) in buffer_edits { + buffer_ids.push(buffer_id); edits.sort_by_key(|edit| edit.range.start); this.buffers.borrow()[&buffer_id] .buffer @@ -844,7 +847,8 @@ impl MultiBuffer { } cx.emit(Event::ExcerptsEdited { - ids: edited_excerpt_ids, + excerpt_ids: edited_excerpt_ids, + buffer_ids, }); } } @@ -1004,7 +1008,9 @@ impl MultiBuffer { this.convert_edits_to_buffer_edits(edits, &snapshot, &[]); drop(snapshot); + let mut buffer_ids = Vec::new(); for (buffer_id, mut edits) in buffer_edits { + buffer_ids.push(buffer_id); edits.sort_unstable_by_key(|edit| edit.range.start); let mut ranges: Vec> = Vec::new(); @@ -1026,7 +1032,8 @@ impl MultiBuffer { } cx.emit(Event::ExcerptsEdited { - ids: edited_excerpt_ids, + excerpt_ids: edited_excerpt_ids, + buffer_ids, }); } }