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 }