Return completion proposals from inline completion providers (#17578)

Updates the inline completion provider to return a completion proposal
which is then converted to a completion state. This completion proposal
includes more detailed information about which inlays specifically
should be rendered.

Release Notes:

- Added support for fill-in-the-middle style inline completions


![image](https://github.com/user-attachments/assets/1830700f-5a76-4d1f-ac6d-246cc69b64c5)
This commit is contained in:
Kevin Wang 2024-09-16 19:57:58 -07:00 committed by GitHub
parent 37b2f4b9d3
commit d315405be1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 166 additions and 38 deletions

View file

@ -1,12 +1,17 @@
use crate::{Supermaven, SupermavenCompletionStateId};
use anyhow::Result;
use client::telemetry::Telemetry;
use editor::{Direction, InlineCompletionProvider};
use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use futures::StreamExt as _;
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use language::{language_settings::all_language_settings, Anchor, Buffer};
use std::{ops::Range, path::Path, sync::Arc, time::Duration};
use text::ToPoint;
use language::{language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot};
use std::{
ops::{AddAssign, Range},
path::Path,
sync::Arc,
time::Duration,
};
use text::{ToOffset, ToPoint};
pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@ -37,6 +42,69 @@ impl SupermavenCompletionProvider {
}
}
// Computes the completion state 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.
fn completion_state_from_diff(
snapshot: BufferSnapshot,
completion_text: &str,
position: Anchor,
delete_range: Range<Anchor>,
) -> CompletionProposal {
let buffer_text = snapshot
.text_for_range(delete_range.clone())
.collect::<String>()
.chars()
.collect::<Vec<char>>();
let mut inlays: Vec<InlayProposal> = Vec::new();
let completion = completion_text.chars().collect::<Vec<char>>();
let mut offset = position.to_offset(&snapshot);
let mut i = 0;
let mut j = 0;
while i < completion.len() && j < buffer_text.len() {
// find the next instance of the buffer text in the completion text.
let k = completion[i..].iter().position(|c| *c == buffer_text[j]);
match k {
Some(k) => {
if k != 0 {
// the range from the current position to item is an inlay.
inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset),
completion_text[i..i + k].into(),
));
offset.add_assign(j);
}
i += k + 1;
j += 1;
}
None => {
// there are no more matching completions, so drop the remaining
// completion text as an inlay.
break;
}
}
}
if j == buffer_text.len() && i < completion.len() {
// there is leftover completion text, so drop it as an inlay.
inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset),
completion_text[i..completion_text.len()].into(),
));
}
CompletionProposal {
inlays,
text: completion_text.into(),
delete_range: Some(delete_range),
}
}
impl InlineCompletionProvider for SupermavenCompletionProvider {
fn name() -> &'static str {
"supermaven"
@ -138,7 +206,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
buffer: &Model<Buffer>,
cursor_position: Anchor,
cx: &'a AppContext,
) -> Option<(&'a str, Option<Range<Anchor>>)> {
) -> Option<CompletionProposal> {
let completion_text = self
.supermaven
.read(cx)
@ -153,7 +221,12 @@ impl InlineCompletionProvider 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_text, Some(range)))
Some(completion_state_from_diff(
snapshot,
completion_text,
cursor_position,
range,
))
} else {
None
}