diff --git a/Cargo.lock b/Cargo.lock index 7768dac710fef..0a3743b69279a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6159,7 +6159,6 @@ version = "0.1.0" dependencies = [ "gpui", "language", - "project", "text", ] diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 85fe20f1ae54f..dc9e0d41daf7c 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -2,7 +2,7 @@ use crate::{Completion, Copilot}; use anyhow::Result; use client::telemetry::Telemetry; use gpui::{AppContext, EntityId, Model, ModelContext, Task}; -use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; +use inline_completion::{CompletionEdit, CompletionProposal, Direction, InlineCompletionProvider}; use language::{ language_settings::{all_language_settings, AllLanguageSettings}, Buffer, OffsetRangeExt, ToOffset, @@ -267,13 +267,12 @@ impl InlineCompletionProvider for CopilotCompletionProvider { if completion_text.trim().is_empty() { None } else { + let position = cursor_position.bias_right(buffer); Some(CompletionProposal { - inlays: vec![InlayProposal::Suggestion( - cursor_position.bias_right(buffer), - completion_text.into(), - )], - text: completion_text.into(), - delete_range: None, + edits: vec![CompletionEdit { + text: completion_text.into(), + range: position..position, + }], }) } } else { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d5d96436e8844..48448bffd8950 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -87,7 +87,7 @@ use hunk_diff::{diff_hunk_to_display, ExpandedHunks}; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use inline_completion::Direction; -use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle}; +use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; use language::{ @@ -437,20 +437,115 @@ pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle { type CompletionId = usize; -#[derive(Clone, Debug)] struct CompletionState { - // render_inlay_ids represents the inlay hints that are inserted - // for rendering the inline completions. They may be discontinuous - // in the event that the completion provider returns some intersection - // with the existing content. - render_inlay_ids: Vec, - // text is the resulting rope that is inserted when the user accepts a completion. - text: Rope, - // position is the position of the cursor when the completion was triggered. - position: multi_buffer::Anchor, - // delete_range is the range of text that this completion state covers. - // if the completion is accepted, this range should be deleted. - delete_range: Option>, + proposal: inline_completion::CompletionProposal, + active_edit: (usize, ComputedCompletionEdit), +} + +impl CompletionState { + pub fn active_edit(&self) -> &ComputedCompletionEdit { + &self.active_edit.1 + } + + pub fn advance( + self, + excerpt_id: ExcerptId, + snapshot: &MultiBufferSnapshot, + ) -> (ComputedCompletionEdit, Option) { + let (curr_ix, old_computed_edit) = self.active_edit; + let next_ix = curr_ix + 1; + + let mut this = None; + if next_ix < self.proposal.edits.len() { + if let Some(computed_edit) = ComputedCompletionEdit::from_edit( + &self.proposal.edits[next_ix], + excerpt_id, + snapshot, + ) { + this = Some(Self { + proposal: self.proposal, + active_edit: (next_ix, computed_edit), + }); + } + } + + (old_computed_edit, this) + } +} + +struct CompletionEditDeletion; + +enum ComputedCompletionEdit { + Insertion { + text: Rope, + position: multi_buffer::Anchor, + render_inlay_ids: Vec, + }, + Diff { + text: Rope, + range: Range, + additions: Vec>, + deletions: Vec>, + }, +} + +impl ComputedCompletionEdit { + fn from_edit( + edit: &inline_completion::CompletionEdit, + excerpt_id: ExcerptId, + snapshot: &MultiBufferSnapshot, + ) -> Option { + let text = edit.text.clone(); + + let start = snapshot.anchor_in_excerpt(excerpt_id, edit.range.start)?; + let end = snapshot.anchor_in_excerpt(excerpt_id, edit.range.end)?; + + let old_text = snapshot.text_for_range(start..end).collect::(); + + if old_text.is_empty() { + Some(Self::Insertion { + position: start, + text, + render_inlay_ids: Vec::new(), + }) + } else { + let new_text = edit.text.to_string(); + let diff = similar::TextDiff::from_chars(&old_text, &new_text); + + let start_offset = start.to_offset(snapshot); + + let mut deletions = Vec::new(); + let mut additions = Vec::new(); + for op in diff.ops() { + let (change, old, new) = op.as_tag_tuple(); + let old = cmp::min(old.start + start_offset, snapshot.len()) + ..cmp::min(old.end + start_offset, snapshot.len()); + match change { + similar::DiffTag::Equal => continue, + similar::DiffTag::Delete => deletions.push(old.to_anchors(snapshot)), + similar::DiffTag::Insert => additions.push(new), + similar::DiffTag::Replace => { + deletions.push(old.to_anchors(snapshot)); + additions.push(new); + } + } + } + + Some(Self::Diff { + text, + range: start..end, + additions, + deletions, + }) + } + } + + pub fn position(&self) -> Anchor { + match self { + ComputedCompletionEdit::Insertion { position, .. } => *position, + ComputedCompletionEdit::Diff { range, .. } => range.start, + } + } } #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)] @@ -2739,7 +2834,8 @@ impl Editor { self.refresh_code_actions(cx); self.refresh_document_highlights(cx); refresh_matching_bracket_highlights(self, cx); - self.discard_inline_completion(false, cx); + // TODO: Figure out how to make this work for old providers + // self.discard_inline_completion(false, cx); linked_editing_ranges::refresh_linked_ranges(self, cx); if self.git_blame_inline_enabled { self.start_inline_blame_timer(cx); @@ -5310,22 +5406,27 @@ impl Editor { _: &AcceptInlineCompletion, cx: &mut ViewContext, ) { - let Some(completion) = self.take_active_inline_completion(cx) else { + if self.move_to_active_inline_completion(cx) { + return; + } + let Some(completion_edit) = self.take_active_inline_completion(false, cx) else { return; }; if let Some(provider) = self.inline_completion_provider() { provider.accept(cx); } + let (selection_range, text) = match completion_edit { + ComputedCompletionEdit::Insertion { position, text, .. } => (position..position, text), + ComputedCompletionEdit::Diff { range, text, .. } => (range, text), + }; + + self.change_selections(None, cx, |s| s.select_ranges([selection_range])); cx.emit(EditorEvent::InputHandled { utf16_range_to_replace: None, - text: completion.text.to_string().into(), + text: text.to_string().into(), }); - - if let Some(range) = completion.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(&text.to_string(), None, cx); self.refresh_inline_completion(true, true, cx); cx.notify(); } @@ -5335,35 +5436,42 @@ impl Editor { _: &AcceptPartialInlineCompletion, cx: &mut ViewContext, ) { + if self.move_to_active_inline_completion(cx) { + return; + } + if self.selections.count() == 1 && self.has_active_inline_completion(cx) { - if let Some(completion) = self.take_active_inline_completion(cx) { - let mut partial_completion = completion - .text - .chars() - .by_ref() - .take_while(|c| c.is_alphabetic()) - .collect::(); - if partial_completion.is_empty() { - partial_completion = completion - .text + let mut is_insertion = false; + if let Some(state) = &self.active_inline_completion { + if let ComputedCompletionEdit::Insertion { text, .. } = state.active_edit() { + let mut partial_completion = text .chars() .by_ref() - .take_while(|c| c.is_whitespace() || !c.is_alphabetic()) + .take_while(|c| c.is_alphabetic()) .collect::(); - } + if partial_completion.is_empty() { + partial_completion = text + .chars() + .by_ref() + .take_while(|c| c.is_whitespace() || !c.is_alphabetic()) + .collect::(); + } - cx.emit(EditorEvent::InputHandled { - utf16_range_to_replace: None, - text: partial_completion.clone().into(), - }); + cx.emit(EditorEvent::InputHandled { + utf16_range_to_replace: None, + text: partial_completion.clone().into(), + }); + + self.insert_with_autoindent_mode(&partial_completion, None, cx); - if let Some(range) = completion.delete_range { - self.change_selections(None, cx, |s| s.select_ranges([range])) + self.refresh_inline_completion(true, true, cx); + is_insertion = true; + cx.notify(); } - self.insert_with_autoindent_mode(&partial_completion, None, cx); + } - self.refresh_inline_completion(true, true, cx); - cx.notify(); + if is_insertion { + self.take_active_inline_completion(false, cx); } } } @@ -5377,34 +5485,75 @@ impl Editor { provider.discard(should_report_inline_completion_event, cx); } - self.take_active_inline_completion(cx).is_some() + self.take_active_inline_completion(true, cx).is_some() } 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.active_edit().position().is_valid(&buffer) + } else { + false + } + } + + fn move_to_active_inline_completion(&mut self, cx: &mut ViewContext) -> bool { + if let Some(target) = self.cursor_needs_repositioning_for_inline_completion(cx) { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges(vec![target..target]) + }); + true } else { false } } + fn cursor_needs_repositioning_for_inline_completion(&self, cx: &AppContext) -> Option { + let completion = self.active_inline_completion.as_ref()?; + + let cursor_position = self.selections.newest_anchor().head(); + let active_edit_position = completion.active_edit().position(); + + let snapshot = self.buffer.read(cx).snapshot(cx); + + // TODO: Decide which approach we want to use to determine if we should move cursor to completion: + // - Check if cursor is on the same line as completion + // - Evaluate if cursor is within N rows of completion + if cursor_position.to_point(&snapshot).row == active_edit_position.to_point(&snapshot).row { + return None; + } + Some(active_edit_position) + } + fn take_active_inline_completion( &mut self, + discard_subsequent_edits: bool, cx: &mut ViewContext, - ) -> Option { - let completion = self.active_inline_completion.take()?; - let render_inlay_ids = completion.render_inlay_ids.clone(); - self.display_map.update(cx, |map, cx| { - map.splice_inlays(render_inlay_ids, Default::default(), cx); - }); - let buffer = self.buffer.read(cx).read(cx); + ) -> Option { + let state = self.active_inline_completion.take()?; + let cursor = self.selections.newest_anchor().head(); + let excerpt_id = cursor.excerpt_id; + let snapshot = self.buffer.read(cx).snapshot(cx); - if completion.position.is_valid(&buffer) { - Some(completion) + let (edit, state) = if discard_subsequent_edits { + (state.active_edit.1, None) } else { - None + state.advance(excerpt_id, &snapshot) + }; + match &edit { + ComputedCompletionEdit::Insertion { + render_inlay_ids, .. + } => { + self.display_map.update(cx, |map, cx| { + map.splice_inlays(render_inlay_ids.clone(), Default::default(), cx); + }); + } + ComputedCompletionEdit::Diff { .. } => { + self.clear_highlights::(cx); + } } + self.active_inline_completion = state; + Some(edit) } fn update_visible_inline_completion(&mut self, cx: &mut ViewContext) { @@ -5417,53 +5566,67 @@ impl Editor { && self.completion_tasks.is_empty() && selection.start == selection.end { - if let Some(provider) = self.inline_completion_provider() { - if let Some((buffer, cursor_buffer_position)) = - self.buffer.read(cx).text_anchor_for_position(cursor, cx) - { - if let Some(proposal) = - provider.active_completion_text(&buffer, cursor_buffer_position, cx) + if self.active_inline_completion.is_none() { + if let Some(provider) = self.inline_completion_provider() { + if let Some((buffer, cursor_buffer_position)) = + self.buffer.read(cx).text_anchor_for_position(cursor, cx) { - let mut to_remove = Vec::new(); - if let Some(completion) = self.active_inline_completion.take() { - to_remove.extend(completion.render_inlay_ids.iter()); + if let Some(proposal) = + provider.active_completion_text(&buffer, cursor_buffer_position, cx) + { + let snapshot = self.buffer.read(cx).snapshot(cx); + if let Some(edit) = proposal.edits.get(0) { + if let Some(computed) = + ComputedCompletionEdit::from_edit(edit, excerpt_id, &snapshot) + { + self.active_inline_completion = Some(CompletionState { + proposal, + active_edit: (0, computed), + }); + } + } } + } + } + } - let to_add = proposal - .inlays - .iter() - .filter_map(|inlay| { - let snapshot = self.buffer.read(cx).snapshot(cx); - let id = post_inc(&mut self.next_inlay_id); - match inlay { - InlayProposal::Hint(position, hint) => { - let position = - snapshot.anchor_in_excerpt(excerpt_id, *position)?; - Some(Inlay::hint(id, position, hint)) - } - InlayProposal::Suggestion(position, text) => { - let position = - snapshot.anchor_in_excerpt(excerpt_id, *position)?; - Some(Inlay::suggestion(id, position, text.clone())) - } - } - }) - .collect_vec(); - - self.active_inline_completion = Some(CompletionState { - position: cursor, - text: proposal.text, - delete_range: proposal.delete_range.and_then(|range| { - let snapshot = self.buffer.read(cx).snapshot(cx); - let start = snapshot.anchor_in_excerpt(excerpt_id, range.start); - let end = snapshot.anchor_in_excerpt(excerpt_id, range.end); - Some(start?..end?) - }), - render_inlay_ids: to_add.iter().map(|i| i.id).collect(), + if let Some(state) = self.active_inline_completion.as_mut() { + let edit = &mut state.active_edit.1; + match edit { + ComputedCompletionEdit::Insertion { + position, + text, + render_inlay_ids, + .. + } => { + let id = post_inc(&mut self.next_inlay_id); + let new_inlays = vec![Inlay::suggestion(id, *position, text.clone())]; + let new_inlay_ids: Vec<_> = new_inlays.iter().map(|i| i.id).collect(); + self.display_map.update(cx, { + let old_inlay_ids = render_inlay_ids.clone(); + move |map, cx| map.splice_inlays(old_inlay_ids, new_inlays.clone(), cx) }); + *render_inlay_ids = new_inlay_ids; - self.display_map - .update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx)); + cx.notify(); + return; + } + ComputedCompletionEdit::Diff { deletions, .. } => { + self.display_map.update(cx, { + let deletions = deletions.clone(); + move |map, cx| { + map.highlight_text( + TypeId::of::(), + deletions, + HighlightStyle { + background_color: Some( + cx.theme().status().deleted_background, + ), + ..Default::default() + }, + ) + } + }); cx.notify(); return; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7f4bc3fb77844..161aa1fefdd23 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -16,13 +16,13 @@ use crate::{ items::BufferSearchHighlights, mouse_context_menu::{self, MenuPosition, MouseContextMenu}, scroll::scroll_amount::ScrollAmount, - BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, - DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, - EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, - HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts, - PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, - CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, - MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, + BlockId, ChunkReplacement, CodeActionsMenu, ComputedCompletionEdit, CursorShape, CustomBlockId, + DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, + EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, + HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, + OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, + ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, + MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; use collections::{BTreeMap, HashMap, HashSet}; @@ -31,7 +31,7 @@ use gpui::{ anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, - FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length, + FontId, GlobalElementId, HighlightStyle, Hitbox, Hsla, InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext, @@ -2724,6 +2724,132 @@ impl EditorElement { true } + #[allow(clippy::too_many_arguments)] + fn layout_inline_completion_popover( + &self, + text_bounds: &Bounds, + editor_snapshot: &EditorSnapshot, + visible_row_range: Range, + line_layouts: &[LineWithInvisibles], + line_height: Pixels, + scroll_pixel_position: gpui::Point, + text_style: &gpui::TextStyle, + cx: &mut WindowContext, + ) -> Option { + const X_OFFSET: f32 = 25.; + const PADDING_Y: f32 = 2.; + + let accept_completion_keystroke = cx.keystroke_text_for_action_in( + &crate::AcceptInlineCompletion, + &self.editor.read(cx).focus_handle, + ); + + let edit = self + .editor + .read(cx) + .active_inline_completion + .as_ref()? + .active_edit(); + + let show_go_to_edit_hint = self + .editor + .read(cx) + .cursor_needs_repositioning_for_inline_completion(cx) + .is_some(); + + let upper_left = edit.position().to_display_point(editor_snapshot); + let is_visible = visible_row_range.contains(&upper_left.row()); + + let container_element = div() + .bg(cx.theme().colors().editor_background) + .border_1() + .border_color(cx.theme().colors().border) + .rounded_md() + .px_1(); + + let (origin, mut element) = if show_go_to_edit_hint { + if is_visible { + let element = container_element + .child(Label::new(format!( + "{} jump to edit", + accept_completion_keystroke + ))) + .into_any(); + + let len = editor_snapshot.line_len(upper_left.row()); + let popup_position = DisplayPoint::new(upper_left.row(), len); + let origin = self.editor.update(cx, |editor, cx| { + editor.display_to_pixel_point(popup_position, editor_snapshot, cx) + })?; + ( + text_bounds.origin + origin + point(px(X_OFFSET), px(0.)), + element, + ) + } else { + let is_above = visible_row_range.start > upper_left.row(); + + let mut element = container_element + .child( + h_flex() + .gap_1() + .child(Label::new(format!( + "{} jump to edit", + accept_completion_keystroke + ))) + .child(Icon::new(if is_above { + IconName::ArrowUp + } else { + IconName::ArrowDown + })), + ) + .into_any(); + + let size = element.layout_as_root(AvailableSpace::min_size(), cx); + + let offset_y = if is_above { + px(PADDING_Y) + } else { + text_bounds.size.height - size.height - px(PADDING_Y) + }; + + let origin = text_bounds.origin + + point((text_bounds.size.width - size.width) / 2., offset_y); + (origin, element) + } + } else if let ComputedCompletionEdit::Diff { + text, additions, .. + } = edit + { + let row = &line_layouts[upper_left.row().minus(visible_row_range.start) as usize]; + let origin = text_bounds.origin + + point( + row.width + px(X_OFFSET) - scroll_pixel_position.x, + upper_left.row().as_f32() * line_height - scroll_pixel_position.y, + ); + + let text = gpui::StyledText::new(text.to_string()).with_highlights( + text_style, + additions.iter().map(|range| { + ( + range.clone(), + HighlightStyle { + background_color: Some(cx.theme().status().created_background), + ..Default::default() + }, + ) + }), + ); + + let element = container_element.child(text).into_any(); + (origin, element) + } else { + return None; + }; + + element.prepaint_as_root(origin, AvailableSpace::min_size(), cx); + Some(element) + } + fn layout_mouse_context_menu( &self, editor_snapshot: &EditorSnapshot, @@ -3952,6 +4078,16 @@ impl EditorElement { } } + fn paint_inline_completion_popover( + &mut self, + layout: &mut EditorLayout, + cx: &mut WindowContext, + ) { + if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() { + inline_completion_popover.paint(cx); + } + } + fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() { mouse_context_menu.paint(cx); @@ -5582,6 +5718,17 @@ impl Element for EditorElement { ); } + let inline_completion_popover = self.layout_inline_completion_popover( + &text_hitbox.bounds, + &snapshot, + start_row..end_row, + &line_layouts, + line_height, + scroll_pixel_position, + &style.text, + cx, + ); + let mouse_context_menu = self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx); @@ -5664,6 +5811,7 @@ impl Element for EditorElement { cursors, visible_cursors, selections, + inline_completion_popover, mouse_context_menu, test_indicators, code_actions_indicator, @@ -5753,6 +5901,7 @@ impl Element for EditorElement { } self.paint_scrollbar(layout, cx); + self.paint_inline_completion_popover(layout, cx); self.paint_mouse_context_menu(layout, cx); }); }) @@ -5808,6 +5957,7 @@ pub struct EditorLayout { test_indicators: Vec, crease_toggles: Vec>, crease_trailers: Vec>, + inline_completion_popover: Option, mouse_context_menu: Option, tab_invisible: ShapedLine, space_invisible: ShapedLine, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 06298a81adb77..64f0128ee2049 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3089,6 +3089,26 @@ impl<'a> WindowContext<'a> { .unwrap_or_else(|| action.name().to_string()) } + /// Represent this action as a key binding string, to display in the UI. + pub fn keystroke_text_for_action_in( + &self, + action: &dyn Action, + focus_handle: &FocusHandle, + ) -> String { + self.bindings_for_action_in(action, focus_handle) + .into_iter() + .next() + .map(|binding| { + binding + .keystrokes() + .iter() + .map(ToString::to_string) + .collect::>() + .join(" ") + }) + .unwrap_or_else(|| action.name().to_string()) + } + /// Dispatch a mouse or keyboard event on the window. #[profiling::function] pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult { diff --git a/crates/inline_completion/Cargo.toml b/crates/inline_completion/Cargo.toml index 237b0ff43ffb4..dcb57769d2db2 100644 --- a/crates/inline_completion/Cargo.toml +++ b/crates/inline_completion/Cargo.toml @@ -14,5 +14,4 @@ path = "src/inline_completion.rs" [dependencies] gpui.workspace = true language.workspace = true -project.workspace = true text.workspace = true diff --git a/crates/inline_completion/src/inline_completion.rs b/crates/inline_completion/src/inline_completion.rs index 689bc0317445f..28f32e4b357ba 100644 --- a/crates/inline_completion/src/inline_completion.rs +++ b/crates/inline_completion/src/inline_completion.rs @@ -1,7 +1,8 @@ +use std::ops::Range; + use gpui::{AppContext, Model, ModelContext}; use language::Buffer; -use std::ops::Range; -use text::{Anchor, Rope}; +use text::Rope; // TODO: Find a better home for `Direction`. // @@ -13,15 +14,13 @@ pub enum Direction { Next, } -pub enum InlayProposal { - Hint(Anchor, project::InlayHint), - Suggestion(Anchor, Rope), +pub struct CompletionProposal { + pub edits: Vec, } -pub struct CompletionProposal { - pub inlays: Vec, +pub struct CompletionEdit { pub text: Rope, - pub delete_range: Option>, + pub range: Range, } pub trait InlineCompletionProvider: 'static + Sized { diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index 5e77cc21ef8e2..2a60e17262763 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -3,7 +3,7 @@ use anyhow::Result; use client::telemetry::Telemetry; use futures::StreamExt as _; use gpui::{AppContext, EntityId, Model, ModelContext, Task}; -use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; +use inline_completion::{CompletionEdit, CompletionProposal, Direction, InlineCompletionProvider}; use language::{language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot}; use std::{ ops::{AddAssign, Range}, @@ -57,7 +57,7 @@ fn completion_state_from_diff( .text_for_range(delete_range.clone()) .collect::(); - let mut inlays: Vec = Vec::new(); + let mut edits: Vec = Vec::new(); let completion_graphemes: Vec<&str> = completion_text.graphemes(true).collect(); let buffer_graphemes: Vec<&str> = buffer_text.graphemes(true).collect(); @@ -74,11 +74,12 @@ fn completion_state_from_diff( match k { Some(k) => { if k != 0 { + let offset = snapshot.anchor_after(offset); // the range from the current position to item is an inlay. - inlays.push(InlayProposal::Suggestion( - snapshot.anchor_after(offset), - completion_graphemes[i..i + k].join("").into(), - )); + edits.push(CompletionEdit { + text: completion_graphemes[i..i + k].join("").into(), + range: offset..offset, + }); } i += k + 1; j += 1; @@ -93,18 +94,15 @@ fn completion_state_from_diff( } if j == buffer_graphemes.len() && i < completion_graphemes.len() { + let offset = snapshot.anchor_after(offset); // there is leftover completion text, so drop it as an inlay. - inlays.push(InlayProposal::Suggestion( - snapshot.anchor_after(offset), - completion_graphemes[i..].join("").into(), - )); + edits.push(CompletionEdit { + text: completion_graphemes[i..].join("").into(), + range: offset..offset, + }); } - CompletionProposal { - inlays, - text: completion_text.into(), - delete_range: Some(delete_range), - } + CompletionProposal { edits } } impl InlineCompletionProvider for SupermavenCompletionProvider {