Detailed changes
@@ -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 {
@@ -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()),
}
@@ -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<usize>, HighlightStyle)>,
+ },
}
pub(crate) enum EditDisplayMode {
@@ -500,9 +501,7 @@ pub(crate) enum EditDisplayMode {
enum InlineCompletion {
Edit {
edits: Vec<(Range<Anchor>, String)>,
- edit_preview: Option<EditPreview>,
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::<usize>(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<Anchor>, String)],
- edit_preview: &EditPreview,
+ editor_snapshot: &EditorSnapshot,
+ edits: &Vec<(Range<Anchor>, String)>,
include_deletions: bool,
cx: &App,
-) -> Option<HighlightedEdits> {
- let edits = edits
- .iter()
- .map(|(anchor, text)| {
- (
- anchor.start.text_anchor..anchor.end.text_anchor,
- text.clone(),
- )
- })
- .collect::<Vec<_>>();
+) -> 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(
@@ -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<Point>, 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::<Vec<_>>();
+#[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::<Vec<_>>();
+ // 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]
@@ -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();
@@ -333,7 +333,6 @@ fn propose_edits<T: ToOffset>(
provider.update(cx, |provider, _| {
provider.set_inline_completion(Some(inline_completion::InlineCompletion {
edits: edits.collect(),
- edit_preview: None,
}))
})
});
@@ -15,7 +15,6 @@ pub enum Direction {
#[derive(Clone)]
pub struct InlineCompletion {
pub edits: Vec<(Range<language::Anchor>, String)>,
- pub edit_preview: Option<language::EditPreview>,
}
pub trait InlineCompletionProvider: 'static + Sized {
@@ -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<usize>, HighlightStyle)>,
-}
-
-impl EditPreview {
- pub fn highlight_edits(
- &self,
- current_snapshot: &BufferSnapshot,
- edits: &[(Range<Anchor>, 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<usize>,
- text: &mut String,
- highlights: &mut Vec<(Range<usize>, HighlightStyle)>,
- override_style: Option<HighlightStyle>,
- 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<usize>) -> 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<Anchor>, String)],
- snapshot: &BufferSnapshot,
- ) -> Option<Range<usize>> {
- 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<T: Into<String>>(base_text: T, cx: &Context<Self>) -> Self {
@@ -996,33 +840,6 @@ impl Buffer {
})
}
- pub fn preview_edits(
- &self,
- edits: Arc<[(Range<Anchor>, String)]>,
- cx: &App,
- ) -> Task<EditPreview> {
- 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.
///
@@ -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<Buffer>,
- cx: &mut TestAppContext,
- edits: impl IntoIterator<Item = (Range<Point>, &'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::<Vec<_>>()
- });
- 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<Buffer>,
- edits: impl IntoIterator<Item = (Range<Point>, &'static str)>,
- cx: &mut TestAppContext,
- ) -> Arc<[(Range<Anchor>, 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::<Vec<_>>()
- })
- .into()
- }
-}
-
#[gpui::test(iterations = 100)]
fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
let min_peers = env::var("MIN_PEERS")
@@ -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::<Vec<_>>();
@@ -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 {
@@ -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<Anchor>, String)]>,
snapshot: BufferSnapshot,
- edit_preview: EditPreview,
input_outline: Arc<str>,
input_events: Arc<str>,
input_excerpt: Arc<str>,
@@ -92,63 +91,55 @@ impl InlineCompletion {
}
fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option<Vec<(Range<Anchor>, String)>> {
- interpolate(&self.snapshot, new_snapshot, self.edits.clone())
- }
-}
-
-fn interpolate(
- old_snapshot: &BufferSnapshot,
- new_snapshot: &BufferSnapshot,
- current_edits: Arc<[(Range<Anchor>, String)]>,
-) -> Option<Vec<(Range<Anchor>, String)>> {
- let mut edits = Vec::new();
-
- let mut user_edits = new_snapshot
- .edits_since::<usize>(&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::<usize>(&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::<String>();
-
- 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::<String>();
+
+ 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<Client>, LlmApiToken, PredictEditsParams) -> R + 'static,
R: Future<Output = Result<PredictEditsResponse>> + 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<Buffer>,
snapshot: &BufferSnapshot,
excerpt_range: Range<usize>,
cursor_offset: usize,
@@ -603,110 +591,52 @@ and then another
cx: &AsyncApp,
) -> Task<Result<Option<InlineCompletion>>> {
let snapshot = snapshot.clone();
- cx.spawn(|cx| async move {
- let output_excerpt: Arc<str> = 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::<Vec<_>>();
+ anyhow::ensure!(
+ start_markers.len() == 1,
+ "expected exactly one start marker, found {}",
+ start_markers.len()
+ );
- let edits: Arc<[(Range<Anchor>, 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<Anchor>, 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::<String>();
- 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<str>,
- excerpt_range: Range<usize>,
- snapshot: &BufferSnapshot,
- ) -> Result<Vec<(Range<Anchor>, String)>> {
- let content = output_excerpt.replace(CURSOR_MARKER, "");
-
- let start_markers = content
- .match_indices(EDITABLE_REGION_START_MARKER)
- .collect::<Vec<_>>();
- 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::<Vec<_>>();
- 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::<Vec<_>>();
- 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::<String>();
-
- 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<Anchor>, 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]