editor/language: hoist out non-generic parts of edit functions. (#3130)

Piotr Osiewicz created

This reduces LLVM IR size of editor (that's one of the heaviest crates
to build) by almost 5%.

LLVM IR size of `editor` before this PR: 3280386
LLVM IR size with `editor::edit` changed: 3227092
LLVM IR size with `editor::edit` and `language::edit` changed: 3146807

Release Notes:
- N/A

Change summary

crates/editor/src/multi_buffer.rs | 140 ++++++++++++++++-------------
crates/language/src/buffer.rs     | 151 +++++++++++++++++---------------
2 files changed, 159 insertions(+), 132 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -498,77 +498,91 @@ impl MultiBuffer {
             }
         }
 
-        for (buffer_id, mut edits) in buffer_edits {
-            edits.sort_unstable_by_key(|edit| edit.range.start);
-            self.buffers.borrow()[&buffer_id]
-                .buffer
-                .update(cx, |buffer, cx| {
-                    let mut edits = edits.into_iter().peekable();
-                    let mut insertions = Vec::new();
-                    let mut original_indent_columns = Vec::new();
-                    let mut deletions = Vec::new();
-                    let empty_str: Arc<str> = "".into();
-                    while let Some(BufferEdit {
-                        mut range,
-                        new_text,
-                        mut is_insertion,
-                        original_indent_column,
-                    }) = edits.next()
-                    {
+        drop(cursor);
+        drop(snapshot);
+        // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
+        fn tail(
+            this: &mut MultiBuffer,
+            buffer_edits: HashMap<u64, Vec<BufferEdit>>,
+            autoindent_mode: Option<AutoindentMode>,
+            edited_excerpt_ids: Vec<ExcerptId>,
+            cx: &mut ModelContext<MultiBuffer>,
+        ) {
+            for (buffer_id, mut edits) in buffer_edits {
+                edits.sort_unstable_by_key(|edit| edit.range.start);
+                this.buffers.borrow()[&buffer_id]
+                    .buffer
+                    .update(cx, |buffer, cx| {
+                        let mut edits = edits.into_iter().peekable();
+                        let mut insertions = Vec::new();
+                        let mut original_indent_columns = Vec::new();
+                        let mut deletions = Vec::new();
+                        let empty_str: Arc<str> = "".into();
                         while let Some(BufferEdit {
-                            range: next_range,
-                            is_insertion: next_is_insertion,
-                            ..
-                        }) = edits.peek()
+                            mut range,
+                            new_text,
+                            mut is_insertion,
+                            original_indent_column,
+                        }) = edits.next()
                         {
-                            if range.end >= next_range.start {
-                                range.end = cmp::max(next_range.end, range.end);
-                                is_insertion |= *next_is_insertion;
-                                edits.next();
-                            } else {
-                                break;
+                            while let Some(BufferEdit {
+                                range: next_range,
+                                is_insertion: next_is_insertion,
+                                ..
+                            }) = edits.peek()
+                            {
+                                if range.end >= next_range.start {
+                                    range.end = cmp::max(next_range.end, range.end);
+                                    is_insertion |= *next_is_insertion;
+                                    edits.next();
+                                } else {
+                                    break;
+                                }
                             }
-                        }
 
-                        if is_insertion {
-                            original_indent_columns.push(original_indent_column);
-                            insertions.push((
-                                buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
-                                new_text.clone(),
-                            ));
-                        } else if !range.is_empty() {
-                            deletions.push((
-                                buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
-                                empty_str.clone(),
-                            ));
+                            if is_insertion {
+                                original_indent_columns.push(original_indent_column);
+                                insertions.push((
+                                    buffer.anchor_before(range.start)
+                                        ..buffer.anchor_before(range.end),
+                                    new_text.clone(),
+                                ));
+                            } else if !range.is_empty() {
+                                deletions.push((
+                                    buffer.anchor_before(range.start)
+                                        ..buffer.anchor_before(range.end),
+                                    empty_str.clone(),
+                                ));
+                            }
                         }
-                    }
 
-                    let deletion_autoindent_mode =
-                        if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                            Some(AutoindentMode::Block {
-                                original_indent_columns: Default::default(),
-                            })
-                        } else {
-                            None
-                        };
-                    let insertion_autoindent_mode =
-                        if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                            Some(AutoindentMode::Block {
-                                original_indent_columns,
-                            })
-                        } else {
-                            None
-                        };
+                        let deletion_autoindent_mode =
+                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+                                Some(AutoindentMode::Block {
+                                    original_indent_columns: Default::default(),
+                                })
+                            } else {
+                                None
+                            };
+                        let insertion_autoindent_mode =
+                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+                                Some(AutoindentMode::Block {
+                                    original_indent_columns,
+                                })
+                            } else {
+                                None
+                            };
 
-                    buffer.edit(deletions, deletion_autoindent_mode, cx);
-                    buffer.edit(insertions, insertion_autoindent_mode, cx);
-                })
-        }
+                        buffer.edit(deletions, deletion_autoindent_mode, cx);
+                        buffer.edit(insertions, insertion_autoindent_mode, cx);
+                    })
+            }
 
-        cx.emit(Event::ExcerptsEdited {
-            ids: edited_excerpt_ids,
-        });
+            cx.emit(Event::ExcerptsEdited {
+                ids: edited_excerpt_ids,
+            });
+        }
+        tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
     }
 
     pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {

crates/language/src/buffer.rs 🔗

@@ -1448,82 +1448,95 @@ impl Buffer {
             return None;
         }
 
-        self.start_transaction();
-        self.pending_autoindent.take();
-        let autoindent_request = autoindent_mode
-            .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
-
-        let edit_operation = self.text.edit(edits.iter().cloned());
-        let edit_id = edit_operation.timestamp();
+        // Non-generic part hoisted out to reduce LLVM IR size.
+        fn tail(
+            this: &mut Buffer,
+            edits: Vec<(Range<usize>, Arc<str>)>,
+            autoindent_mode: Option<AutoindentMode>,
+            cx: &mut ModelContext<Buffer>,
+        ) -> Option<clock::Lamport> {
+            this.start_transaction();
+            this.pending_autoindent.take();
+            let autoindent_request = autoindent_mode
+                .and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode)));
+
+            let edit_operation = this.text.edit(edits.iter().cloned());
+            let edit_id = edit_operation.timestamp();
+
+            if let Some((before_edit, mode)) = autoindent_request {
+                let mut delta = 0isize;
+                let entries = edits
+                    .into_iter()
+                    .enumerate()
+                    .zip(&edit_operation.as_edit().unwrap().new_text)
+                    .map(|((ix, (range, _)), new_text)| {
+                        let new_text_length = new_text.len();
+                        let old_start = range.start.to_point(&before_edit);
+                        let new_start = (delta + range.start as isize) as usize;
+                        delta +=
+                            new_text_length as isize - (range.end as isize - range.start as isize);
+
+                        let mut range_of_insertion_to_indent = 0..new_text_length;
+                        let mut first_line_is_new = false;
+                        let mut original_indent_column = None;
+
+                        // When inserting an entire line at the beginning of an existing line,
+                        // treat the insertion as new.
+                        if new_text.contains('\n')
+                            && old_start.column
+                                <= before_edit.indent_size_for_line(old_start.row).len
+                        {
+                            first_line_is_new = true;
+                        }
 
-        if let Some((before_edit, mode)) = autoindent_request {
-            let mut delta = 0isize;
-            let entries = edits
-                .into_iter()
-                .enumerate()
-                .zip(&edit_operation.as_edit().unwrap().new_text)
-                .map(|((ix, (range, _)), new_text)| {
-                    let new_text_length = new_text.len();
-                    let old_start = range.start.to_point(&before_edit);
-                    let new_start = (delta + range.start as isize) as usize;
-                    delta += new_text_length as isize - (range.end as isize - range.start as isize);
-
-                    let mut range_of_insertion_to_indent = 0..new_text_length;
-                    let mut first_line_is_new = false;
-                    let mut original_indent_column = None;
-
-                    // When inserting an entire line at the beginning of an existing line,
-                    // treat the insertion as new.
-                    if new_text.contains('\n')
-                        && old_start.column <= before_edit.indent_size_for_line(old_start.row).len
-                    {
-                        first_line_is_new = true;
-                    }
+                        // When inserting text starting with a newline, avoid auto-indenting the
+                        // previous line.
+                        if new_text.starts_with('\n') {
+                            range_of_insertion_to_indent.start += 1;
+                            first_line_is_new = true;
+                        }
 
-                    // When inserting text starting with a newline, avoid auto-indenting the
-                    // previous line.
-                    if new_text.starts_with('\n') {
-                        range_of_insertion_to_indent.start += 1;
-                        first_line_is_new = true;
-                    }
+                        // Avoid auto-indenting after the insertion.
+                        if let AutoindentMode::Block {
+                            original_indent_columns,
+                        } = &mode
+                        {
+                            original_indent_column = Some(
+                                original_indent_columns.get(ix).copied().unwrap_or_else(|| {
+                                    indent_size_for_text(
+                                        new_text[range_of_insertion_to_indent.clone()].chars(),
+                                    )
+                                    .len
+                                }),
+                            );
+                            if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
+                                range_of_insertion_to_indent.end -= 1;
+                            }
+                        }
 
-                    // Avoid auto-indenting after the insertion.
-                    if let AutoindentMode::Block {
-                        original_indent_columns,
-                    } = &mode
-                    {
-                        original_indent_column =
-                            Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| {
-                                indent_size_for_text(
-                                    new_text[range_of_insertion_to_indent.clone()].chars(),
-                                )
-                                .len
-                            }));
-                        if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
-                            range_of_insertion_to_indent.end -= 1;
+                        AutoindentRequestEntry {
+                            first_line_is_new,
+                            original_indent_column,
+                            indent_size: before_edit.language_indent_size_at(range.start, cx),
+                            range: this
+                                .anchor_before(new_start + range_of_insertion_to_indent.start)
+                                ..this.anchor_after(new_start + range_of_insertion_to_indent.end),
                         }
-                    }
+                    })
+                    .collect();
 
-                    AutoindentRequestEntry {
-                        first_line_is_new,
-                        original_indent_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),
-                    }
-                })
-                .collect();
+                this.autoindent_requests.push(Arc::new(AutoindentRequest {
+                    before_edit,
+                    entries,
+                    is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
+                }));
+            }
 
-            self.autoindent_requests.push(Arc::new(AutoindentRequest {
-                before_edit,
-                entries,
-                is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
-            }));
+            this.end_transaction(cx);
+            this.send_operation(Operation::Buffer(edit_operation), cx);
+            Some(edit_id)
         }
-
-        self.end_transaction(cx);
-        self.send_operation(Operation::Buffer(edit_operation), cx);
-        Some(edit_id)
+        tail(self, edits, autoindent_mode, cx)
     }
 
     fn did_edit(