Detailed changes
@@ -4321,10 +4321,14 @@ impl Editor {
let snapshot = self.buffer.read(cx).read(cx);
let mut clear_linked_edit_ranges = false;
let mut all_selections_read_only = true;
+ let mut has_adjacent_edits = false;
+ let mut in_adjacent_group = false;
- for (selection, autoclose_region) in
- self.selections_with_autoclose_regions(selections, &snapshot)
- {
+ let mut regions = self
+ .selections_with_autoclose_regions(selections, &snapshot)
+ .peekable();
+
+ while let Some((selection, autoclose_region)) = regions.next() {
if snapshot
.point_to_buffer_point(selection.head())
.is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
@@ -4585,10 +4589,21 @@ impl Editor {
continue;
}
+ let next_is_adjacent = regions
+ .peek()
+ .is_some_and(|(next, _)| selection.end == next.start);
+
// If not handling any auto-close operation, then just replace the selected
// text with the given input and move the selection to the end of the
// newly inserted text.
- let anchor = snapshot.anchor_after(selection.end);
+ let anchor = if in_adjacent_group || next_is_adjacent {
+ // After edits the right bias would shift those anchor to the next visible fragment
+ // but we want to resolve to the previous one
+ snapshot.anchor_before(selection.end)
+ } else {
+ snapshot.anchor_after(selection.end)
+ };
+
if !self.linked_edit_ranges.is_empty() {
let start_anchor = snapshot.anchor_before(selection.start);
@@ -4617,12 +4632,16 @@ impl Editor {
new_selections.push((selection.map(|_| anchor), 0));
edits.push((selection.start..selection.end, text.clone()));
+
+ has_adjacent_edits |= next_is_adjacent;
+ in_adjacent_group = next_is_adjacent;
}
if all_selections_read_only {
return;
}
+ drop(regions);
drop(snapshot);
self.transact(window, cx, |this, window, cx| {
@@ -4633,7 +4652,11 @@ impl Editor {
jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
this.buffer.update(cx, |buffer, cx| {
- buffer.edit(edits, this.autoindent_mode.clone(), cx);
+ if has_adjacent_edits {
+ buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
+ } else {
+ buffer.edit(edits, this.autoindent_mode.clone(), cx);
+ }
});
for (buffer, edits) in linked_edits {
buffer.update(cx, |buffer, cx| {
@@ -520,11 +520,10 @@ struct AutoindentRequest {
struct AutoindentRequestEntry {
/// A range of the buffer whose indentation should be adjusted.
range: Range<Anchor>,
- /// Whether or not these lines should be considered brand new, for the
- /// purpose of auto-indent. When text is not new, its indentation will
- /// only be adjusted if the suggested indentation level has *changed*
- /// since the edit was made.
- first_line_is_new: bool,
+ /// The row of the edit start in the buffer before the edit was applied.
+ /// This is stored here because the anchor in range is created after
+ /// the edit, so it cannot be used with the before_edit snapshot.
+ old_row: Option<u32>,
indent_size: IndentSize,
original_indent_column: Option<u32>,
}
@@ -1953,8 +1952,7 @@ impl Buffer {
let new_end_row = entry.range.end.to_point(&snapshot).row + 1;
language_indent_sizes_by_new_row.push((new_row, entry.indent_size));
- if !entry.first_line_is_new {
- let old_row = position.to_point(&request.before_edit).row;
+ if let Some(old_row) = entry.old_row {
old_to_new_rows.insert(old_row, new_row);
}
row_ranges.push((new_row..new_end_row, entry.original_indent_column));
@@ -2569,7 +2567,7 @@ impl Buffer {
}
/// Applies the given edits to the buffer. Each edit is specified as a range of text to
- /// delete, and a string of text to insert at that location.
+ /// delete, and a string of text to insert at that location. Adjacent edits are coalesced.
///
/// If an [`AutoindentMode`] is provided, then the buffer will enqueue an auto-indent
/// request for the edited ranges, which will be processed when the buffer finishes
@@ -2583,6 +2581,36 @@ impl Buffer {
autoindent_mode: Option<AutoindentMode>,
cx: &mut Context<Self>,
) -> Option<clock::Lamport>
+ where
+ I: IntoIterator<Item = (Range<S>, T)>,
+ S: ToOffset,
+ T: Into<Arc<str>>,
+ {
+ self.edit_internal(edits_iter, autoindent_mode, true, cx)
+ }
+
+ /// Like [`edit`](Self::edit), but does not coalesce adjacent edits.
+ pub fn edit_non_coalesce<I, S, T>(
+ &mut self,
+ edits_iter: I,
+ autoindent_mode: Option<AutoindentMode>,
+ cx: &mut Context<Self>,
+ ) -> Option<clock::Lamport>
+ where
+ I: IntoIterator<Item = (Range<S>, T)>,
+ S: ToOffset,
+ T: Into<Arc<str>>,
+ {
+ self.edit_internal(edits_iter, autoindent_mode, false, cx)
+ }
+
+ fn edit_internal<I, S, T>(
+ &mut self,
+ edits_iter: I,
+ autoindent_mode: Option<AutoindentMode>,
+ coalesce_adjacent: bool,
+ cx: &mut Context<Self>,
+ ) -> Option<clock::Lamport>
where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
@@ -2599,8 +2627,17 @@ impl Buffer {
}
let new_text = new_text.into();
if !new_text.is_empty() || !range.is_empty() {
- if let Some((prev_range, prev_text)) = edits.last_mut()
- && prev_range.end >= range.start
+ let prev_edit = edits.last_mut();
+ let should_coalesce = prev_edit.as_ref().is_some_and(|(prev_range, _)| {
+ if coalesce_adjacent {
+ prev_range.end >= range.start
+ } else {
+ prev_range.end > range.start
+ }
+ });
+
+ if let Some((prev_range, prev_text)) = prev_edit
+ && should_coalesce
{
prev_range.end = cmp::max(prev_range.end, range.end);
*prev_text = format!("{prev_text}{new_text}").into();
@@ -2708,8 +2745,12 @@ impl Buffer {
}
AutoindentRequestEntry {
- first_line_is_new,
original_indent_column,
+ old_row: if first_line_is_new {
+ None
+ } else {
+ Some(old_start.row)
+ },
indent_size: before_edit.language_indent_size_at(range.start, cx),
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
@@ -2757,7 +2798,7 @@ impl Buffer {
.into_iter()
.map(|range| AutoindentRequestEntry {
range: before_edit.anchor_before(range.start)..before_edit.anchor_after(range.end),
- first_line_is_new: true,
+ old_row: None,
indent_size: before_edit.language_indent_size_at(range.start, cx),
original_indent_column: None,
})
@@ -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);
+ }
})
}