editor: Fix the merging of adjacent selection edits (#45363)
Closes #45046 The root of the issue is anchor resolution. When we apply adjacent edits, they get merged into a single edit. In the scenario described in the issue, this is what happens: 1. We create an anchor at the end of each selection (bias::right) on the snapshot before the edits. 2. We collect the edits and apply them to the buffer. 3. Since the edits are adjacent (>=), the buffer merges them into a single edit. 4. As a result, we apply one edit to the text buffer, creating a single visible fragment with length = 3. 5. The buffer ends up with fragments like: [F(len = 3, visible = true), F(len = 1, visible = false), ...] 6. After the edits, we resolve the previously created anchors to produce zero-width selections (cursors). 7. All anchors resolve into deleted fragments, so their resolved offset equals the cumulative visible offset, which is 3. 8. We now have 3 cursors with identical coordinates (0;3). 9. These cursors get merged into a single cursor. I tried several approaches, but they either felt wrong or didn’t work. In particular, I tried adjusting anchor resolution using the delta stored in handle_input, but this doesn’t help because selections are merged immediately after anchor resolution. The only workable solution I found is to avoid anchors entirely for the adjacent-edit case. Instead, we can compute the final cursor positions directly from the edits and create the selections based on that information. Release Notes: - Fixed an issue where adjacent selection insert would merge cursors --------- Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com> Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
This commit is contained in:
1 parent
0821cddcaa
commit
ce99e7ef26
3 files changed
+124
-21
No files matched your search
@@ -1257,6 +1257,33 @@ impl MultiBuffer {
|
||||
I: IntoIterator<Item = (Range<S>, T)>,
|
||||
S: ToOffset,
|
||||
T: Into<Arc<str>>,
|
||||
{
|
||||
self.edit_internal(edits, autoindent_mode, true, cx);
|
||||
}
|
||||
|
||||
pub fn edit_non_coalesce<I, S, T>(
|
||||
&mut self,
|
||||
edits: I,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
cx: &mut Context<Self>,
|
||||
) where
|
||||
I: IntoIterator<Item = (Range<S>, T)>,
|
||||
S: ToOffset,
|
||||
T: Into<Arc<str>>,
|
||||
{
|
||||
self.edit_internal(edits, autoindent_mode, false, cx);
|
||||
}
|
||||
|
||||
fn edit_internal<I, S, T>(
|
||||
&mut self,
|
||||
edits: I,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
coalesce_adjacent: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) where
|
||||
I: IntoIterator<Item = (Range<S>, T)>,
|
||||
S: ToOffset,
|
||||
T: Into<Arc<str>>,
|
||||
{
|
||||
if self.read_only() || self.buffers.is_empty() {
|
||||
return;
|
||||
@@ -1274,13 +1301,14 @@ impl MultiBuffer {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return edit_internal(self, edits, autoindent_mode, cx);
|
||||
return edit_internal(self, edits, autoindent_mode, coalesce_adjacent, cx);
|
||||
|
||||
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
|
||||
fn edit_internal(
|
||||
this: &mut MultiBuffer,
|
||||
edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
|
||||
mut autoindent_mode: Option<AutoindentMode>,
|
||||
coalesce_adjacent: bool,
|
||||
cx: &mut Context<MultiBuffer>,
|
||||
) {
|
||||
let original_indent_columns = match &mut autoindent_mode {
|
||||
@@ -1322,7 +1350,13 @@ impl MultiBuffer {
|
||||
..
|
||||
}) = edits.peek()
|
||||
{
|
||||
if range.end >= next_range.start {
|
||||
let should_coalesce = if coalesce_adjacent {
|
||||
range.end >= next_range.start
|
||||
} else {
|
||||
range.end > next_range.start
|
||||
};
|
||||
|
||||
if should_coalesce {
|
||||
range.end = cmp::max(next_range.end, range.end);
|
||||
is_insertion |= *next_is_insertion;
|
||||
if excerpt_id == *next_excerpt_id {
|
||||
@@ -1365,8 +1399,13 @@ impl MultiBuffer {
|
||||
autoindent_mode.clone()
|
||||
};
|
||||
|
||||
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
||||
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
||||
if coalesce_adjacent {
|
||||
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
||||
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
||||
} else {
|
||||
buffer.edit_non_coalesce(deletions, deletion_autoindent_mode, cx);
|
||||
buffer.edit_non_coalesce(insertions, insertion_autoindent_mode, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user