Fix merging of an update of a symbol with an insert_before operation before the same symbol (#19450)

Nathan Sobo created

When we insert before some text and then update that same text, we need
to preserve and concatenate the new text associated with both
operations.

Release Notes:

- N/A

Change summary

crates/assistant/src/patch.rs | 87 ++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+), 2 deletions(-)

Detailed changes

crates/assistant/src/patch.rs 🔗

@@ -146,12 +146,28 @@ impl ResolvedEdit {
             return false;
         }
 
-        if let Some(description) = &mut self.description {
-            if let Some(other_description) = &other.description {
+        let other_offset_range = other_range.to_offset(buffer);
+        let offset_range = range.to_offset(buffer);
+
+        // If the other range is empty at the start of this edit's range, combine the new text
+        if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
+            self.new_text = format!("{}\n{}", other.new_text, self.new_text);
+            self.range.start = other_range.start;
+
+            if let Some((description, other_description)) =
+                self.description.as_mut().zip(other.description.as_ref())
+            {
+                *description = format!("{}\n{}", other_description, description)
+            }
+        } else {
+            if let Some((description, other_description)) =
+                self.description.as_mut().zip(other.description.as_ref())
+            {
                 description.push('\n');
                 description.push_str(other_description);
             }
         }
+
         true
     }
 }
@@ -699,6 +715,73 @@ mod tests {
             .unindent(),
             cx,
         );
+
+        // Ensure InsertBefore merges correctly with Update of the same text
+
+        assert_edits(
+            "
+                fn foo() {
+
+                }
+            "
+            .unindent(),
+            vec![
+                AssistantEditKind::InsertBefore {
+                    old_text: "
+                        fn foo() {"
+                        .unindent(),
+                    new_text: "
+                        fn bar() {
+                            qux();
+                        }"
+                    .unindent(),
+                    description: "implement bar".into(),
+                },
+                AssistantEditKind::Update {
+                    old_text: "
+                        fn foo() {
+
+                        }"
+                    .unindent(),
+                    new_text: "
+                        fn foo() {
+                            bar();
+                        }"
+                    .unindent(),
+                    description: "call bar in foo".into(),
+                },
+                AssistantEditKind::InsertAfter {
+                    old_text: "
+                        fn foo() {
+
+                        }
+                    "
+                    .unindent(),
+                    new_text: "
+                        fn qux() {
+                            // todo
+                        }
+                    "
+                    .unindent(),
+                    description: "implement qux".into(),
+                },
+            ],
+            "
+                fn bar() {
+                    qux();
+                }
+
+                fn foo() {
+                    bar();
+                }
+
+                fn qux() {
+                    // todo
+                }
+            "
+            .unindent(),
+            cx,
+        );
     }
 
     #[track_caller]