diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 0dc03e40375f506901ae02640f52f679f13df02d..ebcc72c18132136ceb8674a5b2fa926c27915061 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -256,7 +256,6 @@ impl InlineCompletionProvider for CopilotCompletionProvider { let position = cursor_position.bias_right(buffer); Some(InlineCompletion { edits: vec![(position..position, completion_text.into())], - edit_preview: None, }) } } else { diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 7238fc65fe78bcb02888a70e7db8c4ff5ff974a0..50f3eaa0bf4559ea34ecf46c373879cfdad6614d 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -728,13 +728,13 @@ impl CompletionsMenu { } CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint::Loaded { text }) => { match text { - InlineCompletionText::Edit(highlighted_edits) => div() + InlineCompletionText::Edit { text, highlights } => div() .mx_1() .rounded_md() .bg(cx.theme().colors().editor_background) .child( - gpui::StyledText::new(highlighted_edits.text.clone()) - .with_highlights(&style.text, highlighted_edits.highlights.clone()), + gpui::StyledText::new(text.clone()) + .with_highlights(&style.text, highlights.clone()), ), InlineCompletionText::Move(text) => div().child(text.clone()), } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 01b8a8e6db68a8555f9da76239203e22cb7ad400..155ccf7e69efb3fffa0d60ac7aa9b7e524fe3dc9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -96,9 +96,8 @@ use itertools::Itertools; use language::{ language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, - CursorShape, Diagnostic, Documentation, EditPreview, HighlightedEdits, IndentKind, IndentSize, - Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, - TreeSitterOptions, + CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, + Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; @@ -117,7 +116,6 @@ use lsp::{ LanguageServerId, LanguageServerName, }; -use language::BufferSnapshot; use movement::TextLayoutDetails; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo, @@ -488,7 +486,10 @@ impl InlineCompletionMenuHint { #[derive(Clone, Debug)] enum InlineCompletionText { Move(SharedString), - Edit(HighlightedEdits), + Edit { + text: SharedString, + highlights: Vec<(Range, HighlightStyle)>, + }, } pub(crate) enum EditDisplayMode { @@ -500,9 +501,7 @@ pub(crate) enum EditDisplayMode { enum InlineCompletion { Edit { edits: Vec<(Range, String)>, - edit_preview: Option, display_mode: EditDisplayMode, - snapshot: BufferSnapshot, }, Move(Anchor), } @@ -4848,7 +4847,10 @@ impl Editor { selections.select_anchor_ranges([position..position]); }); } - InlineCompletion::Edit { edits, .. } => { + InlineCompletion::Edit { + edits, + display_mode: _, + } => { if let Some(provider) = self.inline_completion_provider() { provider.accept(cx); } @@ -4896,7 +4898,10 @@ impl Editor { selections.select_anchor_ranges([position..position]); }); } - InlineCompletion::Edit { edits, .. } => { + InlineCompletion::Edit { + edits, + display_mode: _, + } => { // Find an insertion that starts at the cursor position. let snapshot = self.buffer.read(cx).snapshot(cx); let cursor_offset = self.selections.newest::(cx).head(); @@ -5035,8 +5040,8 @@ impl Editor { let (buffer, cursor_buffer_position) = self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; - let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?; - let edits = inline_completion + let completion = provider.suggest(&buffer, cursor_buffer_position, cx)?; + let edits = completion .edits .into_iter() .flat_map(|(range, new_text)| { @@ -5061,12 +5066,13 @@ impl Editor { let mut inlay_ids = Vec::new(); let invalidation_row_range; - let completion = if cursor_row < edit_start_row { + let completion; + if cursor_row < edit_start_row { invalidation_row_range = cursor_row..edit_end_row; - InlineCompletion::Move(first_edit_start) + completion = InlineCompletion::Move(first_edit_start); } else if cursor_row > edit_end_row { invalidation_row_range = edit_start_row..cursor_row; - InlineCompletion::Move(first_edit_start) + completion = InlineCompletion::Move(first_edit_start); } else { if edits .iter() @@ -5111,14 +5117,10 @@ impl Editor { EditDisplayMode::DiffPopover }; - let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?; - - InlineCompletion::Edit { + completion = InlineCompletion::Edit { edits, - edit_preview: inline_completion.edit_preview, display_mode, - snapshot, - } + }; }; let invalidation_range = multibuffer @@ -5162,26 +5164,19 @@ impl Editor { let text = match &self.active_inline_completion.as_ref()?.completion { InlineCompletion::Edit { edits, - edit_preview, display_mode: _, - snapshot, - } => edit_preview - .as_ref() - .and_then(|edit_preview| { - inline_completion_edit_text(&snapshot, &edits, edit_preview, true, cx) - }) - .map(InlineCompletionText::Edit), + } => inline_completion_edit_text(&editor_snapshot, edits, true, cx), InlineCompletion::Move(target) => { let target_point = target.to_point(&editor_snapshot.display_snapshot.buffer_snapshot); let target_line = target_point.row + 1; - Some(InlineCompletionText::Move( + InlineCompletionText::Move( format!("Jump to edit in line {}", target_line).into(), - )) + ) } }; - Some(InlineCompletionMenuHint::Loaded { text: text? }) + Some(InlineCompletionMenuHint::Loaded { text }) } else if provider.is_refreshing(cx) { Some(InlineCompletionMenuHint::Loading) } else if provider.needs_terms_acceptance(cx) { @@ -15824,23 +15819,74 @@ pub fn diagnostic_block_renderer( } fn inline_completion_edit_text( - current_snapshot: &BufferSnapshot, - edits: &[(Range, String)], - edit_preview: &EditPreview, + editor_snapshot: &EditorSnapshot, + edits: &Vec<(Range, String)>, include_deletions: bool, cx: &App, -) -> Option { - let edits = edits - .iter() - .map(|(anchor, text)| { - ( - anchor.start.text_anchor..anchor.end.text_anchor, - text.clone(), - ) - }) - .collect::>(); +) -> InlineCompletionText { + let edit_start = edits + .first() + .unwrap() + .0 + .start + .to_display_point(editor_snapshot); + + let mut text = String::new(); + let mut offset = DisplayPoint::new(edit_start.row(), 0).to_offset(editor_snapshot, Bias::Left); + let mut highlights = Vec::new(); + for (old_range, new_text) in edits { + let old_offset_range = old_range.to_offset(&editor_snapshot.buffer_snapshot); + text.extend( + editor_snapshot + .buffer_snapshot + .chunks(offset..old_offset_range.start, false) + .map(|chunk| chunk.text), + ); + offset = old_offset_range.end; + + let start = text.len(); + let color = if include_deletions && new_text.is_empty() { + text.extend( + editor_snapshot + .buffer_snapshot + .chunks(old_offset_range.start..offset, false) + .map(|chunk| chunk.text), + ); + cx.theme().status().deleted_background + } else { + text.push_str(new_text); + cx.theme().status().created_background + }; + let end = text.len(); - Some(edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)) + highlights.push(( + start..end, + HighlightStyle { + background_color: Some(color), + ..Default::default() + }, + )); + } + + let edit_end = edits + .last() + .unwrap() + .0 + .end + .to_display_point(editor_snapshot); + let end_of_line = DisplayPoint::new(edit_end.row(), editor_snapshot.line_len(edit_end.row())) + .to_offset(editor_snapshot, Bias::Right); + text.extend( + editor_snapshot + .buffer_snapshot + .chunks(offset..end_of_line, false) + .map(|chunk| chunk.text), + ); + + InlineCompletionText::Edit { + text: text.into(), + highlights, + } } pub fn highlight_diagnostic_message( diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 66d19bd921dba1fba5f18ec47df4750f1f9f546e..11ee5a3f027f9c0f266e5e85e0888e5b7357b58c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -15258,205 +15258,241 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon } #[gpui::test] -async fn test_inline_completion_text(cx: &mut TestAppContext) { +fn test_inline_completion_text(cx: &mut TestAppContext) { init_test(cx, |_| {}); // Simple insertion - assert_highlighted_edits( - "Hello, world!", - vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())], - true, - cx, - |highlighted_edits, cx| { - assert_eq!(highlighted_edits.text, "Hello, beautiful world!"); - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, 6..16); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(cx.theme().status().created_background) - ); - }, - ) - .await; + { + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple("Hello, world!", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6)); + let edits = vec![(edit_range, " beautiful".to_string())]; + + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, false, cx) + else { + panic!("Failed to generate inline completion text"); + }; + + assert_eq!(text, "Hello, beautiful world!"); + assert_eq!(highlights.len(), 1); + assert_eq!(highlights[0].0, 6..16); + assert_eq!( + highlights[0].1.background_color, + Some(cx.theme().status().created_background) + ); + }) + .unwrap(); + } // Replacement - assert_highlighted_edits( - "This is a test.", - vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())], - false, - cx, - |highlighted_edits, cx| { - assert_eq!(highlighted_edits.text, "That is a test."); - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, 0..4); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(cx.theme().status().created_background) - ); - }, - ) - .await; + { + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple("This is a test.", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edits = vec![( + snapshot.buffer_snapshot.anchor_after(Point::new(0, 0)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)), + "That".to_string(), + )]; + + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, false, cx) + else { + panic!("Failed to generate inline completion text"); + }; + + assert_eq!(text, "That is a test."); + assert_eq!(highlights.len(), 1); + assert_eq!(highlights[0].0, 0..4); + assert_eq!( + highlights[0].1.background_color, + Some(cx.theme().status().created_background) + ); + }) + .unwrap(); + } // Multiple edits - assert_highlighted_edits( - "Hello, world!", - vec![ - (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()), - (Point::new(0, 12)..Point::new(0, 12), " and universe".into()), - ], - false, - cx, - |highlighted_edits, cx| { - assert_eq!(highlighted_edits.text, "Greetings, world and universe!"); - assert_eq!(highlighted_edits.highlights.len(), 2); - assert_eq!(highlighted_edits.highlights[0].0, 0..9); - assert_eq!(highlighted_edits.highlights[1].0, 16..29); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(cx.theme().status().created_background) - ); - assert_eq!( - highlighted_edits.highlights[1].1.background_color, - Some(cx.theme().status().created_background) - ); - }, - ) - .await; + { + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple("Hello, world!", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); - // Multiple lines with edits - assert_highlighted_edits( - "First line\nSecond line\nThird line\nFourth line", - vec![ - (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()), - ( - Point::new(2, 0)..Point::new(2, 10), - "New third line".to_string(), - ), - (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()), - ], - false, - cx, - |highlighted_edits, cx| { - assert_eq!( - highlighted_edits.text, - "Second modified\nNew third line\nFourth updated line" - ); - assert_eq!(highlighted_edits.highlights.len(), 3); - assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified" - assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line" - assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated" - for highlight in &highlighted_edits.highlights { + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edits = vec![ + ( + snapshot.buffer_snapshot.anchor_after(Point::new(0, 0)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)), + "Greetings".into(), + ), + ( + snapshot.buffer_snapshot.anchor_after(Point::new(0, 12)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)), + " and universe".into(), + ), + ]; + + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, false, cx) + else { + panic!("Failed to generate inline completion text"); + }; + + assert_eq!(text, "Greetings, world and universe!"); + assert_eq!(highlights.len(), 2); + assert_eq!(highlights[0].0, 0..9); + assert_eq!(highlights[1].0, 16..29); assert_eq!( - highlight.1.background_color, + highlights[0].1.background_color, Some(cx.theme().status().created_background) ); - } - }, - ) - .await; -} + assert_eq!( + highlights[1].1.background_color, + Some(cx.theme().status().created_background) + ); + }) + .unwrap(); + } -#[gpui::test] -async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) { - init_test(cx, |_| {}); + // Multiple lines with edits + { + let window = cx.add_window(|window, cx| { + let buffer = + MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); - // Deletion - assert_highlighted_edits( - "Hello, world!", - vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())], - true, - cx, - |highlighted_edits, cx| { - assert_eq!(highlighted_edits.text, "Hello, world!"); - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, 5..11); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(cx.theme().status().deleted_background) - ); - }, - ) - .await; + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edits = vec![ + ( + snapshot.buffer_snapshot.anchor_before(Point::new(1, 7)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)), + "modified".to_string(), + ), + ( + snapshot.buffer_snapshot.anchor_before(Point::new(2, 0)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)), + "New third line".to_string(), + ), + ( + snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)), + " updated".to_string(), + ), + ]; - // Insertion - assert_highlighted_edits( - "Hello, world!", - vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())], - true, - cx, - |highlighted_edits, cx| { - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, 6..14); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(cx.theme().status().created_background) - ); - }, - ) - .await; -} + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, false, cx) + else { + panic!("Failed to generate inline completion text"); + }; -async fn assert_highlighted_edits( - text: &str, - edits: Vec<(Range, String)>, - include_deletions: bool, - cx: &mut TestAppContext, - assertion_fn: impl Fn(HighlightedEdits, &App), -) { - let window = cx.add_window(|window, cx| { - let buffer = MultiBuffer::build_simple(text, cx); - Editor::new(EditorMode::Full, buffer, None, true, window, cx) - }); - let cx = &mut VisualTestContext::from_window(*window, cx); + assert_eq!(text, "Second modified\nNew third line\nFourth updated line"); + assert_eq!(highlights.len(), 3); + assert_eq!(highlights[0].0, 7..15); // "modified" + assert_eq!(highlights[1].0, 16..30); // "New third line" + assert_eq!(highlights[2].0, 37..45); // " updated" - let (buffer, snapshot) = window - .update(cx, |editor, _window, cx| { - ( - editor.buffer().clone(), - editor.buffer().read(cx).snapshot(cx), - ) - }) - .unwrap(); + for highlight in &highlights { + assert_eq!( + highlight.1.background_color, + Some(cx.theme().status().created_background) + ); + } + }) + .unwrap(); + } +} - let edits = edits - .into_iter() - .map(|(range, edit)| { - ( - snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end), - edit, - ) - }) - .collect::>(); +#[gpui::test] +fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) { + init_test(cx, |_| {}); - let text_anchor_edits = edits - .clone() - .into_iter() - .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit)) - .collect::>(); + // Deletion + { + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple("Hello, world!", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11)); + let edits = vec![(edit_range, "".to_string())]; + + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, true, cx) + else { + panic!("Failed to generate inline completion text"); + }; - let edit_preview = window - .update(cx, |_, _window, cx| { - buffer - .read(cx) - .as_singleton() - .unwrap() - .read(cx) - .preview_edits(text_anchor_edits.into(), cx) - }) - .unwrap() - .await; + assert_eq!(text, "Hello, world!"); + assert_eq!(highlights.len(), 1); + assert_eq!(highlights[0].0, 5..11); + assert_eq!( + highlights[0].1.background_color, + Some(cx.theme().status().deleted_background) + ); + }) + .unwrap(); + } - cx.update(|_window, cx| { - let highlighted_edits = inline_completion_edit_text( - &snapshot.as_singleton().unwrap().2, - &edits, - &edit_preview, - include_deletions, - cx, - ) - .expect("Missing highlighted edits"); - assertion_fn(highlighted_edits, cx) - }); + // Insertion + { + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple("Hello, world!", cx); + Editor::new(EditorMode::Full, buffer, None, true, window, cx) + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + + window + .update(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6)) + ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6)); + let edits = vec![(edit_range, " digital".to_string())]; + + let InlineCompletionText::Edit { text, highlights } = + inline_completion_edit_text(&snapshot, &edits, true, cx) + else { + panic!("Failed to generate inline completion text"); + }; + + assert_eq!(text, "Hello, digital world!"); + assert_eq!(highlights.len(), 1); + assert_eq!(highlights[0].0, 6..14); + assert_eq!( + highlights[0].1.background_color, + Some(cx.theme().status().created_background) + ); + }) + .unwrap(); + } } #[gpui::test] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 15b8fe58ced19891d9340a50572f668fab7a85b2..328aa44120bf4db7ca646f5b03b90974df7d9ee6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3467,9 +3467,7 @@ impl EditorElement { } InlineCompletion::Edit { edits, - edit_preview, display_mode, - snapshot, } => { if self.editor.read(cx).has_active_completions_menu() { return None; @@ -3522,11 +3520,13 @@ impl EditorElement { EditDisplayMode::DiffPopover => {} } - let highlighted_edits = edit_preview.as_ref().and_then(|edit_preview| { - crate::inline_completion_edit_text(&snapshot, edits, edit_preview, false, cx) - })?; + let crate::InlineCompletionText::Edit { text, highlights } = + crate::inline_completion_edit_text(editor_snapshot, edits, false, cx) + else { + return None; + }; - let line_count = highlighted_edits.text.lines().count() + 1; + let line_count = text.lines().count() + 1; let longest_row = editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1); @@ -3545,14 +3545,15 @@ impl EditorElement { .width }; - let styled_text = gpui::StyledText::new(highlighted_edits.text.clone()) - .with_highlights(&style.text, highlighted_edits.highlights); + let styled_text = + gpui::StyledText::new(text.clone()).with_highlights(&style.text, highlights); let mut element = div() .bg(cx.theme().colors().editor_background) .border_1() .border_color(cx.theme().colors().border) .rounded_md() + .px_1() .child(styled_text) .into_any(); diff --git a/crates/editor/src/inline_completion_tests.rs b/crates/editor/src/inline_completion_tests.rs index 9ba036a93969b6c746ca95cb053c14b8317d2ba3..4d0d8de8dd4004df2c6a7010d4effe536323d933 100644 --- a/crates/editor/src/inline_completion_tests.rs +++ b/crates/editor/src/inline_completion_tests.rs @@ -333,7 +333,6 @@ fn propose_edits( provider.update(cx, |provider, _| { provider.set_inline_completion(Some(inline_completion::InlineCompletion { edits: edits.collect(), - edit_preview: None, })) }) }); diff --git a/crates/inline_completion/src/inline_completion.rs b/crates/inline_completion/src/inline_completion.rs index 5b99dcbb792f2fbdf67c3f4433774070e6d1db7e..db45f7778b68d1fd323bd664bb5bf3da0ead0f4c 100644 --- a/crates/inline_completion/src/inline_completion.rs +++ b/crates/inline_completion/src/inline_completion.rs @@ -15,7 +15,6 @@ pub enum Direction { #[derive(Clone)] pub struct InlineCompletion { pub edits: Vec<(Range, String)>, - pub edit_preview: Option, } pub trait InlineCompletionProvider: 'static + Sized { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index c43736d98217b76fd2f0e1faf696ec3d07aee42f..2ed25237781996584ba7f83bc6a2d458e5256d7d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -25,8 +25,8 @@ use collections::HashMap; use fs::MTime; use futures::channel::oneshot; use gpui::{ - AnyElement, App, AppContext as _, Context, Entity, EventEmitter, HighlightStyle, Pixels, - SharedString, Task, TaskLabel, Window, + AnyElement, App, AppContext as _, Context, Entity, EventEmitter, HighlightStyle, Pixels, Task, + TaskLabel, Window, }; use lsp::LanguageServerId; use parking_lot::Mutex; @@ -65,7 +65,7 @@ pub use text::{ Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint, ToPointUtf16, Transaction, TransactionId, Unclipped, }; -use theme::{ActiveTheme as _, SyntaxTheme}; +use theme::SyntaxTheme; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; use util::{debug_panic, maybe, RangeExt}; @@ -588,162 +588,6 @@ pub struct Runnable { pub buffer: BufferId, } -#[derive(Clone)] -pub struct EditPreview { - applied_edits_snapshot: text::BufferSnapshot, - syntax_snapshot: SyntaxSnapshot, -} - -#[derive(Default, Clone, Debug)] -pub struct HighlightedEdits { - pub text: SharedString, - pub highlights: Vec<(Range, HighlightStyle)>, -} - -impl EditPreview { - pub fn highlight_edits( - &self, - current_snapshot: &BufferSnapshot, - edits: &[(Range, String)], - include_deletions: bool, - cx: &App, - ) -> HighlightedEdits { - let mut text = String::new(); - let mut highlights = Vec::new(); - let Some(range) = self.compute_visible_range(edits, current_snapshot) else { - return HighlightedEdits::default(); - }; - let mut offset = range.start; - let mut delta = 0isize; - - let status_colors = cx.theme().status(); - - for (range, edit_text) in edits { - let edit_range = range.to_offset(current_snapshot); - let new_edit_start = (edit_range.start as isize + delta) as usize; - let new_edit_range = new_edit_start..new_edit_start + edit_text.len(); - - let prev_range = offset..new_edit_start; - - if !prev_range.is_empty() { - let start = text.len(); - self.highlight_text(prev_range, &mut text, &mut highlights, None, cx); - offset += text.len() - start; - } - - if include_deletions && !edit_range.is_empty() { - let start = text.len(); - text.extend(current_snapshot.text_for_range(edit_range.clone())); - let end = text.len(); - - highlights.push(( - start..end, - HighlightStyle { - background_color: Some(status_colors.deleted_background), - ..Default::default() - }, - )); - } - - if !edit_text.is_empty() { - self.highlight_text( - new_edit_range, - &mut text, - &mut highlights, - Some(HighlightStyle { - background_color: Some(status_colors.created_background), - ..Default::default() - }), - cx, - ); - - offset += edit_text.len(); - } - - delta += edit_text.len() as isize - edit_range.len() as isize; - } - - self.highlight_text( - offset..(range.end as isize + delta) as usize, - &mut text, - &mut highlights, - None, - cx, - ); - - HighlightedEdits { - text: text.into(), - highlights, - } - } - - fn highlight_text( - &self, - range: Range, - text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, - override_style: Option, - cx: &App, - ) { - for chunk in self.highlighted_chunks(range) { - let start = text.len(); - text.push_str(chunk.text); - let end = text.len(); - - if let Some(mut highlight_style) = chunk - .syntax_highlight_id - .and_then(|id| id.style(cx.theme().syntax())) - { - if let Some(override_style) = override_style { - highlight_style.highlight(override_style); - } - highlights.push((start..end, highlight_style)); - } else if let Some(override_style) = override_style { - highlights.push((start..end, override_style)); - } - } - } - - fn highlighted_chunks(&self, range: Range) -> BufferChunks { - let captures = - self.syntax_snapshot - .captures(range.clone(), &self.applied_edits_snapshot, |grammar| { - grammar.highlights_query.as_ref() - }); - - let highlight_maps = captures - .grammars() - .iter() - .map(|grammar| grammar.highlight_map()) - .collect(); - - BufferChunks::new( - self.applied_edits_snapshot.as_rope(), - range, - Some((captures, highlight_maps)), - false, - None, - ) - } - - fn compute_visible_range( - &self, - edits: &[(Range, String)], - snapshot: &BufferSnapshot, - ) -> Option> { - let (first, _) = edits.first()?; - let (last, _) = edits.last()?; - - let start = first.start.to_point(snapshot); - let end = last.end.to_point(snapshot); - - // Ensure that the first line of the first edit and the last line of the last edit are always fully visible - let range = Point::new(start.row, 0)..Point::new(end.row, snapshot.line_len(end.row)); - - Some(range.to_offset(&snapshot)) - } -} - impl Buffer { /// Create a new buffer with the given base text. pub fn local>(base_text: T, cx: &Context) -> Self { @@ -996,33 +840,6 @@ impl Buffer { }) } - pub fn preview_edits( - &self, - edits: Arc<[(Range, String)]>, - cx: &App, - ) -> Task { - let registry = self.language_registry(); - let language = self.language().cloned(); - - let mut branch_buffer = self.text.branch(); - let mut syntax_snapshot = self.syntax_map.lock().snapshot(); - cx.background_executor().spawn(async move { - if !edits.is_empty() { - branch_buffer.edit(edits.iter().cloned()); - let snapshot = branch_buffer.snapshot(); - syntax_snapshot.interpolate(&snapshot); - - if let Some(language) = language { - syntax_snapshot.reparse(&snapshot, registry, language); - } - } - EditPreview { - applied_edits_snapshot: branch_buffer.snapshot(), - syntax_snapshot, - } - }) - } - /// Applies all of the changes in this buffer that intersect any of the /// given `ranges` to its base buffer. /// diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index f586ba78bbdc4bea0f5c32a11df2c8f04e71e718..7947bc58dc68d02a8f642866354f18c3a1199b73 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2627,148 +2627,6 @@ fn test_undo_after_merge_into_base(cx: &mut TestAppContext) { branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk")); } -#[gpui::test] -async fn test_preview_edits(cx: &mut TestAppContext) { - cx.update(|cx| { - init_settings(cx, |_| {}); - theme::init(theme::LoadThemes::JustBase, cx); - }); - - let text = indoc! {r#" - struct Person { - first_name: String, - } - - impl Person { - fn last_name(&self) -> &String { - &self.last_name - } - }"# - }; - - let language = Arc::new(Language::new( - LanguageConfig::default(), - Some(tree_sitter_rust::LANGUAGE.into()), - )); - let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx)); - let highlighted_edits = preview_edits( - &buffer, - cx, - [ - (Point::new(5, 7)..Point::new(5, 11), "first"), - (Point::new(6, 14)..Point::new(6, 18), "first"), - ], - ) - .await; - - assert_eq!( - highlighted_edits.text, - " fn lastfirst_name(&self) -> &String {\n &self.lastfirst_name" - ); - - async fn preview_edits( - buffer: &Entity, - cx: &mut TestAppContext, - edits: impl IntoIterator, &'static str)>, - ) -> HighlightedEdits { - let edits = buffer.read_with(cx, |buffer, _| { - edits - .into_iter() - .map(|(range, text)| { - ( - buffer.anchor_before(range.start)..buffer.anchor_after(range.end), - text.to_string(), - ) - }) - .collect::>() - }); - let edit_preview = buffer - .read_with(cx, |buffer, cx| { - buffer.preview_edits(edits.clone().into(), cx) - }) - .await; - cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, true, cx)) - } -} - -#[gpui::test] -async fn test_preview_edits_interpolate(cx: &mut TestAppContext) { - use theme::ActiveTheme; - cx.update(|cx| { - init_settings(cx, |_| {}); - theme::init(theme::LoadThemes::JustBase, cx); - }); - - let text = indoc! {r#" - struct Person { - _name: String - }"# - }; - - let language = Arc::new(Language::new( - LanguageConfig::default(), - Some(tree_sitter_rust::LANGUAGE.into()), - )); - let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx)); - - let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "first")], cx); - let edit_preview = buffer - .read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx)) - .await; - - let highlighted_edits = - cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx)); - - let created_background = cx.read(|cx| cx.theme().status().created_background); - - assert_eq!(highlighted_edits.text, " first_name: String"); - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, 4..9); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(created_background) - ); - - let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "f")], cx); - cx.update(|cx| { - buffer.update(cx, |buffer, cx| { - buffer.edit(edits.iter().cloned(), None, cx); - }) - }); - - let edits = construct_edits(&buffer, [(Point::new(1, 5)..Point::new(1, 5), "irst")], cx); - let highlighted_edits = - cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx)); - - assert_eq!(highlighted_edits.text, " first_name: String"); - assert_eq!(highlighted_edits.highlights.len(), 1); - assert_eq!(highlighted_edits.highlights[0].0, (5..9)); - assert_eq!( - highlighted_edits.highlights[0].1.background_color, - Some(created_background) - ); - - fn construct_edits( - buffer: &Entity, - edits: impl IntoIterator, &'static str)>, - cx: &mut TestAppContext, - ) -> Arc<[(Range, String)]> { - buffer - .read_with(cx, |buffer, _| { - edits - .into_iter() - .map(|(range, text)| { - ( - buffer.anchor_after(range.start)..buffer.anchor_before(range.end), - text.to_string(), - ) - }) - .collect::>() - }) - .into() - } -} - #[gpui::test(iterations = 100)] fn test_random_collaboration(cx: &mut App, mut rng: StdRng) { let min_peers = env::var("MIN_PEERS") diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 2a0c7eaa1cab51bb62dc3102bd7d31755d255fd5..f51eeb96889b2e62756ebf8f1c731f633521c891 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -263,7 +263,7 @@ impl SyntaxSnapshot { self.layers.is_empty() } - pub fn interpolate(&mut self, text: &BufferSnapshot) { + fn interpolate(&mut self, text: &BufferSnapshot) { let edits = text .anchored_edits_since::<(usize, Point)>(&self.interpolated_version) .collect::>(); diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index a46b490e735a3cda498571ac73cad36ffc17ed48..c1666c4c9a8fb10c2eafc2ab3ecd3668f2dcc6c7 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -90,10 +90,7 @@ fn completion_from_diff( edits.push((edit_range, edit_text)); } - InlineCompletion { - edits, - edit_preview: None, - } + InlineCompletion { edits } } impl InlineCompletionProvider for SupermavenCompletionProvider { diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index c2a13d7abfb7dd67cf035d410d33289a105da2f5..e7ee1aa0b5f71aaec25aa3afe0d343e3a74da104 100644 --- a/crates/zeta/src/zeta.rs +++ b/crates/zeta/src/zeta.rs @@ -14,8 +14,8 @@ use gpui::{ }; use http_client::{HttpClient, Method}; use language::{ - language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot, EditPreview, - OffsetRangeExt, Point, ToOffset, ToPoint, + language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot, OffsetRangeExt, + Point, ToOffset, ToPoint, }; use language_models::LlmApiToken; use rpc::{PredictEditsParams, PredictEditsResponse, EXPIRED_LLM_TOKEN_HEADER_NAME}; @@ -76,7 +76,6 @@ pub struct InlineCompletion { cursor_offset: usize, edits: Arc<[(Range, String)]>, snapshot: BufferSnapshot, - edit_preview: EditPreview, input_outline: Arc, input_events: Arc, input_excerpt: Arc, @@ -92,63 +91,55 @@ impl InlineCompletion { } fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option, String)>> { - interpolate(&self.snapshot, new_snapshot, self.edits.clone()) - } -} - -fn interpolate( - old_snapshot: &BufferSnapshot, - new_snapshot: &BufferSnapshot, - current_edits: Arc<[(Range, String)]>, -) -> Option, String)>> { - let mut edits = Vec::new(); - - let mut user_edits = new_snapshot - .edits_since::(&old_snapshot.version) - .peekable(); - for (model_old_range, model_new_text) in current_edits.iter() { - let model_offset_range = model_old_range.to_offset(old_snapshot); - while let Some(next_user_edit) = user_edits.peek() { - if next_user_edit.old.end < model_offset_range.start { - user_edits.next(); - } else { - break; + let mut edits = Vec::new(); + + let mut user_edits = new_snapshot + .edits_since::(&self.snapshot.version) + .peekable(); + for (model_old_range, model_new_text) in self.edits.iter() { + let model_offset_range = model_old_range.to_offset(&self.snapshot); + while let Some(next_user_edit) = user_edits.peek() { + if next_user_edit.old.end < model_offset_range.start { + user_edits.next(); + } else { + break; + } } - } - if let Some(user_edit) = user_edits.peek() { - if user_edit.old.start > model_offset_range.end { - edits.push((model_old_range.clone(), model_new_text.clone())); - } else if user_edit.old == model_offset_range { - let user_new_text = new_snapshot - .text_for_range(user_edit.new.clone()) - .collect::(); - - if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) { - if !model_suffix.is_empty() { - edits.push(( - new_snapshot.anchor_after(user_edit.new.end) - ..new_snapshot.anchor_before(user_edit.new.end), - model_suffix.into(), - )); - } + if let Some(user_edit) = user_edits.peek() { + if user_edit.old.start > model_offset_range.end { + edits.push((model_old_range.clone(), model_new_text.clone())); + } else if user_edit.old == model_offset_range { + let user_new_text = new_snapshot + .text_for_range(user_edit.new.clone()) + .collect::(); + + if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) { + if !model_suffix.is_empty() { + edits.push(( + new_snapshot.anchor_after(user_edit.new.end) + ..new_snapshot.anchor_before(user_edit.new.end), + model_suffix.into(), + )); + } - user_edits.next(); + user_edits.next(); + } else { + return None; + } } else { return None; } } else { - return None; + edits.push((model_old_range.clone(), model_new_text.clone())); } - } else { - edits.push((model_old_range.clone(), model_new_text.clone())); } - } - if edits.is_empty() { - None - } else { - Some(edits) + if edits.is_empty() { + None + } else { + Some(edits) + } } } @@ -310,8 +301,7 @@ impl Zeta { F: FnOnce(Arc, LlmApiToken, PredictEditsParams) -> R + 'static, R: Future> + Send + 'static, { - let buffer = buffer.clone(); - let snapshot = self.report_changes_for_buffer(&buffer, cx); + let snapshot = self.report_changes_for_buffer(buffer, cx); let point = position.to_point(&snapshot); let offset = point.to_offset(&snapshot); let excerpt_range = excerpt_range_for_position(point, &snapshot); @@ -365,7 +355,6 @@ impl Zeta { Self::process_completion_response( output_excerpt, - buffer, &snapshot, excerpt_range, offset, @@ -591,7 +580,6 @@ and then another #[allow(clippy::too_many_arguments)] fn process_completion_response( output_excerpt: String, - buffer: Entity, snapshot: &BufferSnapshot, excerpt_range: Range, cursor_offset: usize, @@ -603,110 +591,52 @@ and then another cx: &AsyncApp, ) -> Task>> { let snapshot = snapshot.clone(); - cx.spawn(|cx| async move { - let output_excerpt: Arc = output_excerpt.into(); + cx.background_executor().spawn(async move { + let content = output_excerpt.replace(CURSOR_MARKER, ""); + + let start_markers = content + .match_indices(EDITABLE_REGION_START_MARKER) + .collect::>(); + anyhow::ensure!( + start_markers.len() == 1, + "expected exactly one start marker, found {}", + start_markers.len() + ); - let edits: Arc<[(Range, String)]> = cx - .background_executor() - .spawn({ - let output_excerpt = output_excerpt.clone(); - let excerpt_range = excerpt_range.clone(); - let snapshot = snapshot.clone(); - async move { Self::parse_edits(output_excerpt, excerpt_range, &snapshot) } - }) - .await? - .into(); - - let Some((edits, snapshot, edit_preview)) = buffer.read_with(&cx, { - let edits = edits.clone(); - |buffer, cx| { - let new_snapshot = buffer.snapshot(); - let edits: Arc<[(Range, String)]> = - interpolate(&snapshot, &new_snapshot, edits)?.into(); - Some((edits.clone(), new_snapshot, buffer.preview_edits(edits, cx))) - } - })? - else { - return anyhow::Ok(None); - }; + let codefence_start = start_markers[0].0; + let content = &content[codefence_start..]; + + let newline_ix = content.find('\n').context("could not find newline")?; + let content = &content[newline_ix + 1..]; + + let codefence_end = content + .rfind(&format!("\n{EDITABLE_REGION_END_MARKER}")) + .context("could not find end marker")?; + let new_text = &content[..codefence_end]; + + let old_text = snapshot + .text_for_range(excerpt_range.clone()) + .collect::(); - let edit_preview = edit_preview.await; + let edits = Self::compute_edits(old_text, new_text, excerpt_range.start, &snapshot); Ok(Some(InlineCompletion { id: InlineCompletionId::new(), path, excerpt_range, cursor_offset, - edits, - edit_preview, - snapshot, + edits: edits.into(), + snapshot: snapshot.clone(), input_outline: input_outline.into(), input_events: input_events.into(), input_excerpt: input_excerpt.into(), - output_excerpt, + output_excerpt: output_excerpt.into(), request_sent_at, response_received_at: Instant::now(), })) }) } - fn parse_edits( - output_excerpt: Arc, - excerpt_range: Range, - snapshot: &BufferSnapshot, - ) -> Result, String)>> { - let content = output_excerpt.replace(CURSOR_MARKER, ""); - - let start_markers = content - .match_indices(EDITABLE_REGION_START_MARKER) - .collect::>(); - anyhow::ensure!( - start_markers.len() == 1, - "expected exactly one start marker, found {}", - start_markers.len() - ); - - let end_markers = content - .match_indices(EDITABLE_REGION_END_MARKER) - .collect::>(); - anyhow::ensure!( - end_markers.len() == 1, - "expected exactly one end marker, found {}", - end_markers.len() - ); - - let sof_markers = content - .match_indices(START_OF_FILE_MARKER) - .collect::>(); - anyhow::ensure!( - sof_markers.len() <= 1, - "expected at most one start-of-file marker, found {}", - sof_markers.len() - ); - - let codefence_start = start_markers[0].0; - let content = &content[codefence_start..]; - - let newline_ix = content.find('\n').context("could not find newline")?; - let content = &content[newline_ix + 1..]; - - let codefence_end = content - .rfind(&format!("\n{EDITABLE_REGION_END_MARKER}")) - .context("could not find end marker")?; - let new_text = &content[..codefence_end]; - - let old_text = snapshot - .text_for_range(excerpt_range.clone()) - .collect::(); - - Ok(Self::compute_edits( - old_text, - new_text, - excerpt_range.start, - &snapshot, - )) - } - pub fn compute_edits( old_text: String, new_text: &str, @@ -1259,7 +1189,6 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide Some(inline_completion::InlineCompletion { edits: edits[edit_start_ix..edit_end_ix].to_vec(), - edit_preview: Some(completion.edit_preview.clone()), }) } } @@ -1278,24 +1207,18 @@ mod tests { use super::*; #[gpui::test] - async fn test_inline_completion_basic_interpolation(cx: &mut TestAppContext) { + fn test_inline_completion_basic_interpolation(cx: &mut TestAppContext) { let buffer = cx.new(|cx| Buffer::local("Lorem ipsum dolor", cx)); - let edits: Arc<[(Range, String)]> = cx.update(|cx| { - to_completion_edits( - [(2..5, "REM".to_string()), (9..11, "".to_string())], - &buffer, - cx, - ) - .into() - }); - - let edit_preview = cx - .read(|cx| buffer.read(cx).preview_edits(edits.clone(), cx)) - .await; - let completion = InlineCompletion { - edits, - edit_preview, + edits: cx + .read(|cx| { + to_completion_edits( + [(2..5, "REM".to_string()), (9..11, "".to_string())], + &buffer, + cx, + ) + }) + .into(), path: Path::new("").into(), snapshot: cx.read(|cx| buffer.read(cx).snapshot()), id: InlineCompletionId::new(), @@ -1309,89 +1232,106 @@ mod tests { response_received_at: Instant::now(), }; - cx.update(|cx| { - assert_eq!( + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(2..5, "REM".to_string()), (9..11, "".to_string())] - ); + cx, + ) + }), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(2..2, "REM".to_string()), (6..8, "".to_string())] - ); + cx, + ) + }), + vec![(2..2, "REM".to_string()), (6..8, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.undo(cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.undo(cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(2..5, "REM".to_string()), (9..11, "".to_string())] - ); + cx, + ) + }), + vec![(2..5, "REM".to_string()), (9..11, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "R")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(2..5, "R")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(3..3, "EM".to_string()), (7..9, "".to_string())] - ); + cx, + ) + }), + vec![(3..3, "EM".to_string()), (7..9, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "E")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "E")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(4..4, "M".to_string()), (8..10, "".to_string())] - ); + cx, + ) + }), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "M")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "M")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(9..11, "".to_string())] - ); + cx, + ) + }), + vec![(9..11, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(4..4, "M".to_string()), (8..10, "".to_string())] - ); + cx, + ) + }), + vec![(4..4, "M".to_string()), (8..10, "".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(8..10, "")], None, cx)); - assert_eq!( + buffer.update(cx, |buffer, cx| buffer.edit([(8..10, "")], None, cx)); + assert_eq!( + cx.read(|cx| { from_completion_edits( &completion.interpolate(&buffer.read(cx).snapshot()).unwrap(), &buffer, - cx - ), - vec![(4..4, "M".to_string())] - ); + cx, + ) + }), + vec![(4..4, "M".to_string())] + ); - buffer.update(cx, |buffer, cx| buffer.edit([(4..6, "")], None, cx)); - assert_eq!(completion.interpolate(&buffer.read(cx).snapshot()), None); - }) + buffer.update(cx, |buffer, cx| buffer.edit([(4..6, "")], None, cx)); + assert_eq!( + cx.read(|cx| completion.interpolate(&buffer.read(cx).snapshot())), + None + ); } #[gpui::test]