Fix prefix/suffix calculation when determining copilot suggestion

Antonio Scandurra , Nathan Sobo , and Mikayla Maki created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/editor/src/editor.rs       | 34 +++++++++++++++-----------------
crates/editor/src/multi_buffer.rs |  4 +++
crates/text/src/text.rs           |  8 +++++++
3 files changed, 28 insertions(+), 18 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1041,29 +1041,26 @@ impl CopilotState {
         cursor: Anchor,
         buffer: &MultiBufferSnapshot,
     ) -> Option<&str> {
+        use language::ToOffset as _;
+
         let completion = self.completions.get(self.active_completion_index)?;
         let excerpt_id = self.excerpt_id?;
-        let completion_buffer_id = buffer.buffer_id_for_excerpt(excerpt_id);
-        let completion_start = Anchor {
-            excerpt_id,
-            buffer_id: completion_buffer_id,
-            text_anchor: completion.range.start,
-        };
-        let completion_end = Anchor {
-            excerpt_id,
-            buffer_id: completion_buffer_id,
-            text_anchor: completion.range.end,
-        };
-        let prefix_len = common_prefix(buffer.chars_at(completion_start), completion.text.chars());
+        let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
+
+        let mut completion_range = completion.range.to_offset(&completion_buffer);
+        let prefix_len = common_prefix(
+            completion_buffer.chars_for_range(completion_range.clone()),
+            completion.text.chars(),
+        );
+        completion_range.start += prefix_len;
         let suffix_len = common_prefix(
-            buffer.reversed_chars_at(completion_end),
-            completion.text.chars().rev(),
+            completion_buffer.reversed_chars_for_range(completion_range.clone()),
+            completion.text[prefix_len..].chars().rev(),
         );
+        completion_range.end = completion_range.end.saturating_sub(suffix_len);
 
-        let prefix_end_offset = completion_start.to_offset(&buffer) + prefix_len;
-        let suffix_start_offset = completion_end.to_offset(&buffer) - suffix_len;
-        if prefix_end_offset == suffix_start_offset
-            && prefix_end_offset == cursor.to_offset(&buffer)
+        if completion_range.is_empty()
+            && completion_range.start == cursor.text_anchor.to_offset(&completion_buffer)
         {
             Some(&completion.text[prefix_len..completion.text.len() - suffix_len])
         } else {
@@ -6492,6 +6489,7 @@ impl Editor {
             multi_buffer::Event::Edited => {
                 self.refresh_active_diagnostics(cx);
                 self.refresh_code_actions(cx);
+                self.refresh_copilot_suggestions(cx);
                 cx.emit(Event::BufferEdited);
             }
             multi_buffer::Event::ExcerptsAdded {

crates/editor/src/multi_buffer.rs 🔗

@@ -2933,6 +2933,10 @@ impl MultiBufferSnapshot {
         Some(self.excerpt(excerpt_id)?.buffer_id)
     }
 
+    pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> {
+        Some(&self.excerpt(excerpt_id)?.buffer)
+    }
+
     fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> {
         let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
         let locator = self.excerpt_locator_for_id(excerpt_id);

crates/text/src/text.rs 🔗

@@ -1579,6 +1579,14 @@ impl BufferSnapshot {
         self.text_for_range(range).flat_map(str::chars)
     }
 
+    pub fn reversed_chars_for_range<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> impl Iterator<Item = char> + '_ {
+        self.reversed_chunks_in_range(range)
+            .flat_map(|chunk| chunk.chars().rev())
+    }
+
     pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
     where
         T: ToOffset,