Truncate line when accepting inline suggestions for Supermaven (#13884)

Configures inline completions to delete the remaining text on the given
line. This doesn't affect the github copilot inline completion provider
since it seems to only generate suggestions if the cursor is at the end
of the line but fixes the usability issues related to Supermaven.




https://github.com/user-attachments/assets/1b8bc9a3-4666-4665-a436-96e4beee01bb





Release Notes:

- Fixed https://github.com/zed-industries/zed/issues/13039

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Kevin Wang 2024-07-22 14:59:38 -04:00 committed by GitHub
parent c703e20a06
commit a20e92a8c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 51 additions and 19 deletions

1
Cargo.lock generated
View file

@ -10538,6 +10538,7 @@ dependencies = [
"settings", "settings",
"smol", "smol",
"supermaven_api", "supermaven_api",
"text",
"theme", "theme",
"ui", "ui",
"util", "util",

View file

@ -8,7 +8,7 @@ use language::{
Buffer, OffsetRangeExt, ToOffset, Buffer, OffsetRangeExt, ToOffset,
}; };
use settings::Settings; use settings::Settings;
use std::{path::Path, sync::Arc, time::Duration}; use std::{ops::Range, path::Path, sync::Arc, time::Duration};
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@ -239,7 +239,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
cursor_position: language::Anchor, cursor_position: language::Anchor,
cx: &'a AppContext, cx: &'a AppContext,
) -> Option<&'a str> { ) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
let buffer_id = buffer.entity_id(); let buffer_id = buffer.entity_id();
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let completion = self.active_completion()?; let completion = self.active_completion()?;
@ -269,7 +269,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
if completion_text.trim().is_empty() { if completion_text.trim().is_empty() {
None None
} else { } else {
Some(completion_text) Some((completion_text, None))
} }
} else { } else {
None None

View file

@ -533,7 +533,7 @@ pub struct Editor {
gutter_hovered: bool, gutter_hovered: bool,
hovered_link_state: Option<HoveredLinkState>, hovered_link_state: Option<HoveredLinkState>,
inline_completion_provider: Option<RegisteredInlineCompletionProvider>, inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
active_inline_completion: Option<Inlay>, active_inline_completion: Option<(Inlay, Option<Range<Anchor>>)>,
show_inline_completions: bool, show_inline_completions: bool,
inlay_hint_cache: InlayHintCache, inlay_hint_cache: InlayHintCache,
expanded_hunks: ExpandedHunks, expanded_hunks: ExpandedHunks,
@ -4953,7 +4953,7 @@ impl Editor {
_: &AcceptInlineCompletion, _: &AcceptInlineCompletion,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let Some(completion) = self.take_active_inline_completion(cx) else { let Some((completion, delete_range)) = self.take_active_inline_completion(cx) else {
return; return;
}; };
if let Some(provider) = self.inline_completion_provider() { if let Some(provider) = self.inline_completion_provider() {
@ -4964,6 +4964,10 @@ impl Editor {
utf16_range_to_replace: None, utf16_range_to_replace: None,
text: completion.text.to_string().into(), text: completion.text.to_string().into(),
}); });
if let Some(range) = delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx); self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
self.refresh_inline_completion(true, cx); self.refresh_inline_completion(true, cx);
cx.notify(); cx.notify();
@ -4975,7 +4979,7 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
if self.selections.count() == 1 && self.has_active_inline_completion(cx) { if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
if let Some(completion) = self.take_active_inline_completion(cx) { if let Some((completion, delete_range)) = self.take_active_inline_completion(cx) {
let mut partial_completion = completion let mut partial_completion = completion
.text .text
.chars() .chars()
@ -4995,7 +4999,12 @@ impl Editor {
utf16_range_to_replace: None, utf16_range_to_replace: None,
text: partial_completion.clone().into(), text: partial_completion.clone().into(),
}); });
if let Some(range) = delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&partial_completion, None, cx); self.insert_with_autoindent_mode(&partial_completion, None, cx);
self.refresh_inline_completion(true, cx); self.refresh_inline_completion(true, cx);
cx.notify(); cx.notify();
} }
@ -5017,20 +5026,23 @@ impl Editor {
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool { pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
if let Some(completion) = self.active_inline_completion.as_ref() { if let Some(completion) = self.active_inline_completion.as_ref() {
let buffer = self.buffer.read(cx).read(cx); let buffer = self.buffer.read(cx).read(cx);
completion.position.is_valid(&buffer) completion.0.position.is_valid(&buffer)
} else { } else {
false false
} }
} }
fn take_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> { fn take_active_inline_completion(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Inlay, Option<Range<Anchor>>)> {
let completion = self.active_inline_completion.take()?; let completion = self.active_inline_completion.take()?;
self.display_map.update(cx, |map, cx| { self.display_map.update(cx, |map, cx| {
map.splice_inlays(vec![completion.id], Default::default(), cx); map.splice_inlays(vec![completion.0.id], Default::default(), cx);
}); });
let buffer = self.buffer.read(cx).read(cx); let buffer = self.buffer.read(cx).read(cx);
if completion.position.is_valid(&buffer) { if completion.0.position.is_valid(&buffer) {
Some(completion) Some(completion)
} else { } else {
None None
@ -5041,6 +5053,8 @@ impl Editor {
let selection = self.selections.newest_anchor(); let selection = self.selections.newest_anchor();
let cursor = selection.head(); let cursor = selection.head();
let excerpt_id = cursor.excerpt_id;
if self.context_menu.read().is_none() if self.context_menu.read().is_none()
&& self.completion_tasks.is_empty() && self.completion_tasks.is_empty()
&& selection.start == selection.end && selection.start == selection.end
@ -5049,18 +5063,28 @@ impl Editor {
if let Some((buffer, cursor_buffer_position)) = if let Some((buffer, cursor_buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx) self.buffer.read(cx).text_anchor_for_position(cursor, cx)
{ {
if let Some(text) = if let Some((text, text_anchor_range)) =
provider.active_completion_text(&buffer, cursor_buffer_position, cx) provider.active_completion_text(&buffer, cursor_buffer_position, cx)
{ {
let text = Rope::from(text); let text = Rope::from(text);
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
if let Some(completion) = self.active_inline_completion.take() { if let Some(completion) = self.active_inline_completion.take() {
to_remove.push(completion.id); to_remove.push(completion.0.id);
} }
let completion_inlay = let completion_inlay =
Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text); Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
self.active_inline_completion = Some(completion_inlay.clone());
let multibuffer_anchor_range = text_anchor_range.and_then(|range| {
let snapshot = self.buffer.read(cx).snapshot(cx);
Some(
snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?,
)
});
self.active_inline_completion =
Some((completion_inlay.clone(), multibuffer_anchor_range));
self.display_map.update(cx, move |map, cx| { self.display_map.update(cx, move |map, cx| {
map.splice_inlays(to_remove, vec![completion_inlay], cx) map.splice_inlays(to_remove, vec![completion_inlay], cx)
}); });

View file

@ -1,6 +1,7 @@
use crate::Direction; use crate::Direction;
use gpui::{AppContext, Model, ModelContext}; use gpui::{AppContext, Model, ModelContext};
use language::Buffer; use language::Buffer;
use std::ops::Range;
pub trait InlineCompletionProvider: 'static + Sized { pub trait InlineCompletionProvider: 'static + Sized {
fn name() -> &'static str; fn name() -> &'static str;
@ -31,7 +32,7 @@ pub trait InlineCompletionProvider: 'static + Sized {
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
cursor_position: language::Anchor, cursor_position: language::Anchor,
cx: &'a AppContext, cx: &'a AppContext,
) -> Option<&'a str>; ) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
} }
pub trait InlineCompletionProviderHandle { pub trait InlineCompletionProviderHandle {
@ -62,7 +63,7 @@ pub trait InlineCompletionProviderHandle {
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
cursor_position: language::Anchor, cursor_position: language::Anchor,
cx: &'a AppContext, cx: &'a AppContext,
) -> Option<&'a str>; ) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
} }
impl<T> InlineCompletionProviderHandle for Model<T> impl<T> InlineCompletionProviderHandle for Model<T>
@ -117,7 +118,7 @@ where
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
cursor_position: language::Anchor, cursor_position: language::Anchor,
cx: &'a AppContext, cx: &'a AppContext,
) -> Option<&'a str> { ) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
self.read(cx) self.read(cx)
.active_completion_text(buffer, cursor_position, cx) .active_completion_text(buffer, cursor_position, cx)
} }

View file

@ -27,6 +27,7 @@ serde_json.workspace = true
settings.workspace = true settings.workspace = true
supermaven_api.workspace = true supermaven_api.workspace = true
smol.workspace = true smol.workspace = true
text.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true

View file

@ -5,7 +5,8 @@ use editor::{Direction, InlineCompletionProvider};
use futures::StreamExt as _; use futures::StreamExt as _;
use gpui::{AppContext, EntityId, Model, ModelContext, Task}; use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use language::{language_settings::all_language_settings, Anchor, Buffer}; use language::{language_settings::all_language_settings, Anchor, Buffer};
use std::{path::Path, sync::Arc, time::Duration}; use std::{ops::Range, path::Path, sync::Arc, time::Duration};
use text::ToPoint;
pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@ -139,7 +140,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
cursor_position: Anchor, cursor_position: Anchor,
cx: &'a AppContext, cx: &'a AppContext,
) -> Option<&'a str> { ) -> Option<(&'a str, Option<Range<Anchor>>)> {
let completion_text = self let completion_text = self
.supermaven .supermaven
.read(cx) .read(cx)
@ -150,7 +151,11 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
let completion_text = completion_text.trim_end(); let completion_text = completion_text.trim_end();
if !completion_text.trim().is_empty() { if !completion_text.trim().is_empty() {
Some(completion_text) 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);
Some((completion_text, Some(range)))
} else { } else {
None None
} }