From a20e92a8c196af5df07c9ea250b12da431a57895 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 22 Jul 2024 14:59:38 -0400 Subject: [PATCH] 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 Co-authored-by: Conrad Irwin --- Cargo.lock | 1 + .../src/copilot_completion_provider.rs | 6 +-- crates/editor/src/editor.rs | 44 ++++++++++++++----- .../editor/src/inline_completion_provider.rs | 7 +-- crates/supermaven/Cargo.toml | 1 + .../src/supermaven_completion_provider.rs | 11 +++-- 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ceccb8a4cac4193d1c251f8db5112ea95d7bc585..50c0ce21e9671d255f7cd35ff52dea39709d2e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10538,6 +10538,7 @@ dependencies = [ "settings", "smol", "supermaven_api", + "text", "theme", "ui", "util", diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index cc5da74cb1c98be242fd96c3f4444a155af080a4..c9d254f51b2e469bd6f882059cc0650dc8b04858 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -8,7 +8,7 @@ use language::{ Buffer, OffsetRangeExt, ToOffset, }; 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); @@ -239,7 +239,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider { buffer: &Model, cursor_position: language::Anchor, cx: &'a AppContext, - ) -> Option<&'a str> { + ) -> Option<(&'a str, Option>)> { let buffer_id = buffer.entity_id(); let buffer = buffer.read(cx); let completion = self.active_completion()?; @@ -269,7 +269,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider { if completion_text.trim().is_empty() { None } else { - Some(completion_text) + Some((completion_text, None)) } } else { None diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 14b1f14df69b494cb068c6612ed8d79d535ccea7..c05d6bdc29db9860c2b6f783b2459ae4fe209767 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -533,7 +533,7 @@ pub struct Editor { gutter_hovered: bool, hovered_link_state: Option, inline_completion_provider: Option, - active_inline_completion: Option, + active_inline_completion: Option<(Inlay, Option>)>, show_inline_completions: bool, inlay_hint_cache: InlayHintCache, expanded_hunks: ExpandedHunks, @@ -4953,7 +4953,7 @@ impl Editor { _: &AcceptInlineCompletion, cx: &mut ViewContext, ) { - let Some(completion) = self.take_active_inline_completion(cx) else { + let Some((completion, delete_range)) = self.take_active_inline_completion(cx) else { return; }; if let Some(provider) = self.inline_completion_provider() { @@ -4964,6 +4964,10 @@ impl Editor { utf16_range_to_replace: None, 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.refresh_inline_completion(true, cx); cx.notify(); @@ -4975,7 +4979,7 @@ impl Editor { cx: &mut ViewContext, ) { 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 .text .chars() @@ -4995,7 +4999,12 @@ impl Editor { utf16_range_to_replace: None, 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.refresh_inline_completion(true, cx); cx.notify(); } @@ -5017,20 +5026,23 @@ impl Editor { pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool { if let Some(completion) = self.active_inline_completion.as_ref() { let buffer = self.buffer.read(cx).read(cx); - completion.position.is_valid(&buffer) + completion.0.position.is_valid(&buffer) } else { false } } - fn take_active_inline_completion(&mut self, cx: &mut ViewContext) -> Option { + fn take_active_inline_completion( + &mut self, + cx: &mut ViewContext, + ) -> Option<(Inlay, Option>)> { let completion = self.active_inline_completion.take()?; 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); - if completion.position.is_valid(&buffer) { + if completion.0.position.is_valid(&buffer) { Some(completion) } else { None @@ -5041,6 +5053,8 @@ impl Editor { let selection = self.selections.newest_anchor(); let cursor = selection.head(); + let excerpt_id = cursor.excerpt_id; + if self.context_menu.read().is_none() && self.completion_tasks.is_empty() && selection.start == selection.end @@ -5049,18 +5063,28 @@ impl Editor { if let Some((buffer, cursor_buffer_position)) = 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) { let text = Rope::from(text); let mut to_remove = Vec::new(); if let Some(completion) = self.active_inline_completion.take() { - to_remove.push(completion.id); + to_remove.push(completion.0.id); } let completion_inlay = 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| { map.splice_inlays(to_remove, vec![completion_inlay], cx) }); diff --git a/crates/editor/src/inline_completion_provider.rs b/crates/editor/src/inline_completion_provider.rs index 3fd9135c46edde75529d518cf3439ece8c4950aa..b7516419b9cb12b03c84774ae881651b8d27a4f5 100644 --- a/crates/editor/src/inline_completion_provider.rs +++ b/crates/editor/src/inline_completion_provider.rs @@ -1,6 +1,7 @@ use crate::Direction; use gpui::{AppContext, Model, ModelContext}; use language::Buffer; +use std::ops::Range; pub trait InlineCompletionProvider: 'static + Sized { fn name() -> &'static str; @@ -31,7 +32,7 @@ pub trait InlineCompletionProvider: 'static + Sized { buffer: &Model, cursor_position: language::Anchor, cx: &'a AppContext, - ) -> Option<&'a str>; + ) -> Option<(&'a str, Option>)>; } pub trait InlineCompletionProviderHandle { @@ -62,7 +63,7 @@ pub trait InlineCompletionProviderHandle { buffer: &Model, cursor_position: language::Anchor, cx: &'a AppContext, - ) -> Option<&'a str>; + ) -> Option<(&'a str, Option>)>; } impl InlineCompletionProviderHandle for Model @@ -117,7 +118,7 @@ where buffer: &Model, cursor_position: language::Anchor, cx: &'a AppContext, - ) -> Option<&'a str> { + ) -> Option<(&'a str, Option>)> { self.read(cx) .active_completion_text(buffer, cursor_position, cx) } diff --git a/crates/supermaven/Cargo.toml b/crates/supermaven/Cargo.toml index 41a454dc053cde21d617d0cdfdf0369b573c3900..b1a676349099a2b1fe4fc9ede7c8fad6365c0d59 100644 --- a/crates/supermaven/Cargo.toml +++ b/crates/supermaven/Cargo.toml @@ -27,6 +27,7 @@ serde_json.workspace = true settings.workspace = true supermaven_api.workspace = true smol.workspace = true +text.workspace = true ui.workspace = true util.workspace = true diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index 46a71bb1e56221d0d947c9fc69b10877670e1ea9..41a7339718b39e4f8831eeda60fd5d6726a38184 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -5,7 +5,8 @@ use editor::{Direction, InlineCompletionProvider}; use futures::StreamExt as _; use gpui::{AppContext, EntityId, Model, ModelContext, Task}; 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); @@ -139,7 +140,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider { buffer: &Model, cursor_position: Anchor, cx: &'a AppContext, - ) -> Option<&'a str> { + ) -> Option<(&'a str, Option>)> { let completion_text = self .supermaven .read(cx) @@ -150,7 +151,11 @@ impl InlineCompletionProvider for SupermavenCompletionProvider { let completion_text = completion_text.trim_end(); 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 { None }