Unfold buffers in multibuffers when editing them (#25677)

Release Notes:

- Multibuffers: Unfold excerpts when editing their contents.

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
João Marcos 2025-02-26 17:39:33 -03:00 committed by GitHub
parent b5a1ae6526
commit be1ac78e11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 98 additions and 43 deletions

View file

@ -330,7 +330,11 @@ impl DisplayMap {
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive); block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
} }
pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) { pub fn fold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@ -341,10 +345,14 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); 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<Self>) { pub fn unfold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@ -355,7 +363,7 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); 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 { pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {

View file

@ -1239,26 +1239,45 @@ impl<'a> BlockMapWriter<'a> {
self.remove(blocks_to_remove); self.remove(blocks_to_remove);
} }
pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) { pub fn fold_buffers(
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(
&mut self, &mut self,
buffer_id: BufferId, buffer_ids: impl IntoIterator<Item = BufferId>,
multi_buffer: &MultiBuffer, multi_buffer: &MultiBuffer,
cx: &App, 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<Item = BufferId>,
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<Item = BufferId>,
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(); 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( let last_edit_row = cmp::min(
wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1, wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
wrap_snapshot.max_point().row(), wrap_snapshot.max_point().row(),
@ -2730,7 +2749,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { 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 { let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
@ -2805,7 +2824,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { 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_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@ -2861,7 +2880,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { 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_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@ -2922,7 +2941,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { 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_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@ -3000,7 +3019,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { 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_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@ -3250,7 +3269,7 @@ mod tests {
); );
folded_count += 1; folded_count += 1;
unfolded_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 { if unfold {
let buffer_to_unfold = let buffer_to_unfold =
@ -3258,7 +3277,7 @@ mod tests {
log::info!("Unfolding {buffer_to_unfold:?}"); log::info!("Unfolding {buffer_to_unfold:?}");
unfolded_count += 1; unfolded_count += 1;
folded_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!( log::info!(
"Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}" "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"

View file

@ -13314,8 +13314,9 @@ impl Editor {
return; return;
} }
let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
self.display_map self.display_map.update(cx, |display_map, cx| {
.update(cx, |display_map, cx| display_map.fold_buffer(buffer_id, cx)); display_map.fold_buffers([buffer_id], cx)
});
cx.emit(EditorEvent::BufferFoldToggled { cx.emit(EditorEvent::BufferFoldToggled {
ids: folded_excerpts.iter().map(|&(id, _)| id).collect(), ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
folded: true, folded: true,
@ -13329,7 +13330,7 @@ impl Editor {
} }
let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
self.display_map.update(cx, |display_map, 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 { cx.emit(EditorEvent::BufferFoldToggled {
ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(), ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
@ -15212,8 +15213,16 @@ impl Editor {
.retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some()); .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() }) cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
} }
multi_buffer::Event::ExcerptsEdited { ids } => { multi_buffer::Event::ExcerptsEdited {
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() }) 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 } => { multi_buffer::Event::ExcerptsExpanded { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);

View file

@ -15644,7 +15644,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
} }
#[gpui::test] #[gpui::test]
async fn test_multi_buffer_folding(cx: &mut TestAppContext) { async fn test_folding_buffers(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); 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| { let multi_buffer_editor = cx.new_window_entity(|window, cx| {
Editor::new( Editor::new(
EditorMode::Full, EditorMode::Full,
multi_buffer, multi_buffer.clone(),
Some(project.clone()), Some(project.clone()),
true, true,
window, 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!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), 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| { 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" "After unfolding the second buffer, its text should be displayed"
); );
multi_buffer_editor.update(cx, |editor, cx| { // Typing inside of buffer 1 causes that buffer to be unfolded.
editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx) 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::<String>(),
"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!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), 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" "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!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), 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" "After unfolding the all buffers, all original text should be displayed"
); );
} }
#[gpui::test] #[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, |_| {}); init_test(cx, |_| {});
let sample_text_1 = "1111\n2222\n3333".to_string(); 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] #[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, |_| {}); init_test(cx, |_| {});
let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();

View file

@ -98,7 +98,8 @@ pub enum Event {
ids: Vec<ExcerptId>, ids: Vec<ExcerptId>,
}, },
ExcerptsEdited { ExcerptsEdited {
ids: Vec<ExcerptId>, excerpt_ids: Vec<ExcerptId>,
buffer_ids: Vec<BufferId>,
}, },
DiffHunksToggled, DiffHunksToggled,
Edited { Edited {
@ -767,7 +768,9 @@ impl MultiBuffer {
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_start_columns); this.convert_edits_to_buffer_edits(edits, &snapshot, &original_start_columns);
drop(snapshot); drop(snapshot);
let mut buffer_ids = Vec::new();
for (buffer_id, mut edits) in buffer_edits { for (buffer_id, mut edits) in buffer_edits {
buffer_ids.push(buffer_id);
edits.sort_by_key(|edit| edit.range.start); edits.sort_by_key(|edit| edit.range.start);
this.buffers.borrow()[&buffer_id] this.buffers.borrow()[&buffer_id]
.buffer .buffer
@ -844,7 +847,8 @@ impl MultiBuffer {
} }
cx.emit(Event::ExcerptsEdited { 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, &[]); this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
drop(snapshot); drop(snapshot);
let mut buffer_ids = Vec::new();
for (buffer_id, mut edits) in buffer_edits { for (buffer_id, mut edits) in buffer_edits {
buffer_ids.push(buffer_id);
edits.sort_unstable_by_key(|edit| edit.range.start); edits.sort_unstable_by_key(|edit| edit.range.start);
let mut ranges: Vec<Range<usize>> = Vec::new(); let mut ranges: Vec<Range<usize>> = Vec::new();
@ -1026,7 +1032,8 @@ impl MultiBuffer {
} }
cx.emit(Event::ExcerptsEdited { cx.emit(Event::ExcerptsEdited {
ids: edited_excerpt_ids, excerpt_ids: edited_excerpt_ids,
buffer_ids,
}); });
} }
} }