Derive indent size from the language at the cursor when auto-indenting

Max Brunsfeld created

Change summary

crates/editor/src/multi_buffer.rs | 16 ++++++++-
crates/language/src/buffer.rs     | 52 +++++++++++++++++++++++---------
2 files changed, 51 insertions(+), 17 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -1967,8 +1967,10 @@ impl MultiBufferSnapshot {
 
         let mut rows_for_excerpt = Vec::new();
         let mut cursor = self.excerpts.cursor::<Point>();
-
         let mut rows = rows.into_iter().peekable();
+        let mut prev_row = u32::MAX;
+        let mut prev_language_indent_size = IndentSize::default();
+
         while let Some(row) = rows.next() {
             cursor.seek(&Point::new(row, 0), Bias::Right, &());
             let excerpt = match cursor.item() {
@@ -1976,7 +1978,17 @@ impl MultiBufferSnapshot {
                 _ => continue,
             };
 
-            let single_indent_size = excerpt.buffer.single_indent_size(cx);
+            // Retrieve the language and indent size once for each disjoint region being indented.
+            let single_indent_size = if row.saturating_sub(1) == prev_row {
+                prev_language_indent_size
+            } else {
+                excerpt
+                    .buffer
+                    .language_indent_size_at(Point::new(row, 0), cx)
+            };
+            prev_language_indent_size = single_indent_size;
+            prev_row = row;
+
             let start_buffer_row = excerpt.range.context.start.to_point(&excerpt.buffer).row;
             let start_multibuffer_row = cursor.start().row;
 

crates/language/src/buffer.rs 🔗

@@ -84,14 +84,15 @@ pub struct BufferSnapshot {
     parse_count: usize,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
 pub struct IndentSize {
     pub len: u32,
     pub kind: IndentKind,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
 pub enum IndentKind {
+    #[default]
     Space,
     Tab,
 }
@@ -236,7 +237,6 @@ pub enum AutoindentMode {
 struct AutoindentRequest {
     before_edit: BufferSnapshot,
     entries: Vec<AutoindentRequestEntry>,
-    indent_size: IndentSize,
     is_block_mode: bool,
 }
 
@@ -249,6 +249,7 @@ struct AutoindentRequestEntry {
     /// only be adjusted if the suggested indentation level has *changed*
     /// since the edit was made.
     first_line_is_new: bool,
+    indent_size: IndentSize,
     original_indent_column: Option<u32>,
 }
 
@@ -794,10 +795,13 @@ impl Buffer {
                 // buffer before this batch of edits.
                 let mut row_ranges = Vec::new();
                 let mut old_to_new_rows = BTreeMap::new();
+                let mut language_indent_sizes_by_new_row = Vec::new();
                 for entry in &request.entries {
                     let position = entry.range.start;
                     let new_row = position.to_point(&snapshot).row;
                     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;
                         old_to_new_rows.insert(old_row, new_row);
@@ -811,6 +815,8 @@ impl Buffer {
                 let mut old_suggestions = BTreeMap::<u32, IndentSize>::default();
                 let old_edited_ranges =
                     contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
+                let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
+                let mut language_indent_size = IndentSize::default();
                 for old_edited_range in old_edited_ranges {
                     let suggestions = request
                         .before_edit
@@ -819,6 +825,17 @@ impl Buffer {
                         .flatten();
                     for (old_row, suggestion) in old_edited_range.zip(suggestions) {
                         if let Some(suggestion) = suggestion {
+                            let new_row = *old_to_new_rows.get(&old_row).unwrap();
+
+                            // Find the indent size based on the language for this row.
+                            while let Some((row, size)) = language_indent_sizes.peek() {
+                                if *row > new_row {
+                                    break;
+                                }
+                                language_indent_size = *size;
+                                language_indent_sizes.next();
+                            }
+
                             let suggested_indent = old_to_new_rows
                                 .get(&suggestion.basis_row)
                                 .and_then(|from_row| old_suggestions.get(from_row).copied())
@@ -827,9 +844,8 @@ impl Buffer {
                                         .before_edit
                                         .indent_size_for_line(suggestion.basis_row)
                                 })
-                                .with_delta(suggestion.delta, request.indent_size);
-                            old_suggestions
-                                .insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent);
+                                .with_delta(suggestion.delta, language_indent_size);
+                            old_suggestions.insert(new_row, suggested_indent);
                         }
                     }
                     yield_now().await;
@@ -850,6 +866,8 @@ impl Buffer {
 
                 // Compute new suggestions for each line, but only include them in the result
                 // if they differ from the old suggestion for that line.
+                let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
+                let mut language_indent_size = IndentSize::default();
                 for new_edited_row_range in new_edited_row_ranges {
                     let suggestions = snapshot
                         .suggest_autoindents(new_edited_row_range.clone())
@@ -857,13 +875,22 @@ impl Buffer {
                         .flatten();
                     for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
                         if let Some(suggestion) = suggestion {
+                            // Find the indent size based on the language for this row.
+                            while let Some((row, size)) = language_indent_sizes.peek() {
+                                if *row > new_row {
+                                    break;
+                                }
+                                language_indent_size = *size;
+                                language_indent_sizes.next();
+                            }
+
                             let suggested_indent = indent_sizes
                                 .get(&suggestion.basis_row)
                                 .copied()
                                 .unwrap_or_else(|| {
                                     snapshot.indent_size_for_line(suggestion.basis_row)
                                 })
-                                .with_delta(suggestion.delta, request.indent_size);
+                                .with_delta(suggestion.delta, language_indent_size);
                             if old_suggestions
                                 .get(&new_row)
                                 .map_or(true, |old_indentation| {
@@ -1194,7 +1221,6 @@ impl Buffer {
         let edit_id = edit_operation.local_timestamp();
 
         if let Some((before_edit, mode)) = autoindent_request {
-            let indent_size = before_edit.single_indent_size(cx);
             let (start_columns, is_block_mode) = match mode {
                 AutoindentMode::Block {
                     original_indent_columns: start_columns,
@@ -1243,6 +1269,7 @@ impl Buffer {
                     AutoindentRequestEntry {
                         first_line_is_new,
                         original_indent_column: start_column,
+                        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),
                     }
@@ -1252,7 +1279,6 @@ impl Buffer {
             self.autoindent_requests.push(Arc::new(AutoindentRequest {
                 before_edit,
                 entries,
-                indent_size,
                 is_block_mode,
             }));
         }
@@ -1570,8 +1596,8 @@ impl BufferSnapshot {
         indent_size_for_line(self, row)
     }
 
-    pub fn single_indent_size(&self, cx: &AppContext) -> IndentSize {
-        let language_name = self.language().map(|language| language.name());
+    pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
+        let language_name = self.language_at(position).map(|language| language.name());
         let settings = cx.global::<Settings>();
         if settings.hard_tabs(language_name.as_deref()) {
             IndentSize::tab()
@@ -1832,10 +1858,6 @@ impl BufferSnapshot {
         }
     }
 
-    pub fn language(&self) -> Option<&Arc<Language>> {
-        self.language.as_ref()
-    }
-
     pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
         let offset = position.to_offset(self);
         self.syntax