From fcc3d1092fc2c0323d6f06e93e06c5ed8fad2c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20L=C3=BCthy?= Date: Fri, 29 Aug 2025 22:17:22 +0200 Subject: [PATCH] supermaven: Improve completion caching and position validation (#37047) Closes #36981 - Add completion text and position caching to reduce redundant API calls - Only trigger new completion requests on text changes, not cursor movement - Validate cursor position to ensure completions show at correct location - Improve end-of-line range calculation for more accurate deletions - Extract reset_completion_cache helper for cleaner code organization - Update completion diff algorithm documentation for clarity Edit: Sorry this is the 2nd PR, I forgot that the forks history was messy; I cherrypicked and cleaned it properly with this PR Release Notes: - supermaven: Improved caching of predictions - supermaven: Fixed an issue where changing cursor position would incorrectly trigger new completions --- .../src/supermaven_completion_provider.rs | 93 +++++++++++++++---- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index eb54c83f8126002a19728e51b282b98191707717..89c5129822d94229cd1644587f15f4a4de2bf86a 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -19,8 +19,10 @@ pub struct SupermavenCompletionProvider { supermaven: Entity, buffer_id: Option, completion_id: Option, + completion_text: Option, file_extension: Option, pending_refresh: Option>>, + completion_position: Option, } impl SupermavenCompletionProvider { @@ -29,16 +31,19 @@ impl SupermavenCompletionProvider { supermaven, buffer_id: None, completion_id: None, + completion_text: None, file_extension: None, pending_refresh: None, + completion_position: None, } } } // Computes the edit prediction from the difference between the completion text. -// this is defined by greedily matching the buffer text against the completion text, with any leftover buffer placed at the end. -// for example, given the completion text "moo cows are cool" and the buffer text "cowsre pool", the completion state would be -// the inlays "moo ", " a", and "cool" which will render as "[moo ]cows[ a]re [cool]pool" in the editor. +// This is defined by greedily matching the buffer text against the completion text. +// Inlays are inserted for parts of the completion text that are not present in the buffer text. +// For example, given the completion text "axbyc" and the buffer text "xy", the rendered output in the editor would be "[a]x[b]y[c]". +// The parts in brackets are the inlays. fn completion_from_diff( snapshot: BufferSnapshot, completion_text: &str, @@ -133,6 +138,14 @@ impl EditPredictionProvider for SupermavenCompletionProvider { debounce: bool, cx: &mut Context, ) { + // Only make new completion requests when debounce is true (i.e., when text is typed) + // When debounce is false (i.e., cursor movement), we should not make new requests + if !debounce { + return; + } + + reset_completion_cache(self, cx); + let Some(mut completion) = self.supermaven.update(cx, |supermaven, cx| { supermaven.complete(&buffer_handle, cursor_position, cx) }) else { @@ -146,6 +159,17 @@ impl EditPredictionProvider for SupermavenCompletionProvider { while let Some(()) = completion.updates.next().await { this.update(cx, |this, cx| { + // Get the completion text and cache it + if let Some(text) = + this.supermaven + .read(cx) + .completion(&buffer_handle, cursor_position, cx) + { + this.completion_text = Some(text.to_string()); + + this.completion_position = Some(cursor_position); + } + this.completion_id = Some(completion.id); this.buffer_id = Some(buffer_handle.entity_id()); this.file_extension = buffer_handle.read(cx).file().and_then(|file| { @@ -156,7 +180,6 @@ impl EditPredictionProvider for SupermavenCompletionProvider { .to_string(), ) }); - this.pending_refresh = None; cx.notify(); })?; } @@ -174,13 +197,11 @@ impl EditPredictionProvider for SupermavenCompletionProvider { } fn accept(&mut self, _cx: &mut Context) { - self.pending_refresh = None; - self.completion_id = None; + reset_completion_cache(self, _cx); } fn discard(&mut self, _cx: &mut Context) { - self.pending_refresh = None; - self.completion_id = None; + reset_completion_cache(self, _cx); } fn suggest( @@ -189,10 +210,34 @@ impl EditPredictionProvider for SupermavenCompletionProvider { cursor_position: Anchor, cx: &mut Context, ) -> Option { - let completion_text = self - .supermaven - .read(cx) - .completion(buffer, cursor_position, cx)?; + if self.buffer_id != Some(buffer.entity_id()) { + return None; + } + + if self.completion_id.is_none() { + return None; + } + + let completion_text = if let Some(cached_text) = &self.completion_text { + cached_text.as_str() + } else { + let text = self + .supermaven + .read(cx) + .completion(buffer, cursor_position, cx)?; + self.completion_text = Some(text.to_string()); + text + }; + + // Check if the cursor is still at the same position as the completion request + // If we don't have a completion position stored, don't show the completion + if let Some(completion_position) = self.completion_position { + if cursor_position != completion_position { + return None; + } + } else { + return None; + } let completion_text = trim_to_end_of_line_unless_leading_newline(completion_text); @@ -200,15 +245,20 @@ impl EditPredictionProvider for SupermavenCompletionProvider { if !completion_text.trim().is_empty() { let snapshot = buffer.read(cx).snapshot(); - let mut point = cursor_position.to_point(&snapshot); - point.column = snapshot.line_len(point.row); - let range = cursor_position..snapshot.anchor_after(point); + + // Calculate the range from cursor to end of line correctly + let cursor_point = cursor_position.to_point(&snapshot); + let end_of_line = snapshot.anchor_after(language::Point::new( + cursor_point.row, + snapshot.line_len(cursor_point.row), + )); + let delete_range = cursor_position..end_of_line; Some(completion_from_diff( snapshot, completion_text, cursor_position, - range, + delete_range, )) } else { None @@ -216,6 +266,17 @@ impl EditPredictionProvider for SupermavenCompletionProvider { } } +fn reset_completion_cache( + provider: &mut SupermavenCompletionProvider, + _cx: &mut Context, +) { + provider.pending_refresh = None; + provider.completion_id = None; + provider.completion_text = None; + provider.completion_position = None; + provider.buffer_id = None; +} + fn trim_to_end_of_line_unless_leading_newline(text: &str) -> &str { if has_leading_newline(text) { text