completions: Add subtle/eager behavior to Supermaven and Copilot (#35548)

This pull request introduces changes to improve the behavior and
consistency of multiple completion providers
(`CopilotCompletionProvider`, `SupermavenCompletionProvider`) and their
integration with UI elements like menus and inline completion buttons.
It now allows to see the prediction with the completion menu open whilst
pressing `opt` and also enables the subtle/eager setting that was
introduced with zeta.

Edit: I managed to get the preview working with correct icons!
<img width="909" height="232" alt="image"
src="https://github.com/user-attachments/assets/65800e67-4bc4-40f8-be78-806fcfe74ad9"
/>
<img width="1460" height="318" alt="CleanShot 2025-08-04 at 01 36 31@2x"
src="https://github.com/user-attachments/assets/15651405-720f-465f-a13c-c7470817810a"
/>

Correct icons are also displayed:
<img width="244" height="96" alt="image"
src="https://github.com/user-attachments/assets/0b8a687f-73e3-452d-aefb-784c52831b73"
/>


Edit2: I added some comments, would be very happy to receive feedback
(still learning rust)

Release Notes:

- Added Subtle and Eager edit prediction modes to Copilot and Supermaven
This commit is contained in:
Raphael Lüthy 2025-08-07 17:27:29 +02:00 committed by GitHub
parent dd840e4b27
commit 2234220618
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 372 additions and 63 deletions

View file

@ -234,16 +234,14 @@ fn find_relevant_completion<'a>(
}
let original_cursor_offset = buffer.clip_offset(state.prefix_offset, text::Bias::Left);
let text_inserted_since_completion_request =
buffer.text_for_range(original_cursor_offset..current_cursor_offset);
let mut trimmed_completion = state_completion;
for chunk in text_inserted_since_completion_request {
if let Some(suffix) = trimmed_completion.strip_prefix(chunk) {
trimmed_completion = suffix;
} else {
continue 'completions;
}
}
let text_inserted_since_completion_request: String = buffer
.text_for_range(original_cursor_offset..current_cursor_offset)
.collect();
let trimmed_completion =
match state_completion.strip_prefix(&text_inserted_since_completion_request) {
Some(suffix) => suffix,
None => continue 'completions,
};
if best_completion.map_or(false, |best| best.len() > trimmed_completion.len()) {
continue;
@ -439,3 +437,77 @@ pub struct SupermavenCompletion {
pub id: SupermavenCompletionStateId,
pub updates: watch::Receiver<()>,
}
#[cfg(test)]
mod tests {
use super::*;
use collections::BTreeMap;
use gpui::TestAppContext;
use language::Buffer;
#[gpui::test]
async fn test_find_relevant_completion_no_first_letter_skip(cx: &mut TestAppContext) {
let buffer = cx.new(|cx| Buffer::local("hello world", cx));
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let mut states = BTreeMap::new();
let state_id = SupermavenCompletionStateId(1);
let (updates_tx, _) = watch::channel();
states.insert(
state_id,
SupermavenCompletionState {
buffer_id: buffer.entity_id(),
prefix_anchor: buffer_snapshot.anchor_before(0), // Start of buffer
prefix_offset: 0,
text: "hello".to_string(),
dedent: String::new(),
updates_tx,
},
);
let cursor_position = buffer_snapshot.anchor_after(1);
let result = find_relevant_completion(
&states,
buffer.entity_id(),
&buffer_snapshot,
cursor_position,
);
assert_eq!(result, Some("ello"));
}
#[gpui::test]
async fn test_find_relevant_completion_with_multiple_chars(cx: &mut TestAppContext) {
let buffer = cx.new(|cx| Buffer::local("hello world", cx));
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let mut states = BTreeMap::new();
let state_id = SupermavenCompletionStateId(1);
let (updates_tx, _) = watch::channel();
states.insert(
state_id,
SupermavenCompletionState {
buffer_id: buffer.entity_id(),
prefix_anchor: buffer_snapshot.anchor_before(0), // Start of buffer
prefix_offset: 0,
text: "hello".to_string(),
dedent: String::new(),
updates_tx,
},
);
let cursor_position = buffer_snapshot.anchor_after(3);
let result = find_relevant_completion(
&states,
buffer.entity_id(),
&buffer_snapshot,
cursor_position,
);
assert_eq!(result, Some("lo"));
}
}

View file

@ -108,6 +108,14 @@ impl EditPredictionProvider for SupermavenCompletionProvider {
}
fn show_completions_in_menu() -> bool {
true
}
fn show_tab_accept_marker() -> bool {
true
}
fn supports_jump_to_edit() -> bool {
false
}
@ -116,7 +124,7 @@ impl EditPredictionProvider for SupermavenCompletionProvider {
}
fn is_refreshing(&self) -> bool {
self.pending_refresh.is_some()
self.pending_refresh.is_some() && self.completion_id.is_none()
}
fn refresh(
@ -197,6 +205,7 @@ impl EditPredictionProvider for SupermavenCompletionProvider {
let mut point = cursor_position.to_point(&snapshot);
point.column = snapshot.line_len(point.row);
let range = cursor_position..snapshot.anchor_after(point);
Some(completion_from_diff(
snapshot,
completion_text,