use edit_prediction_types::{ EditPredictionDelegate, EditPredictionIconSet, EditPredictionRequestTrigger, PredictedCursorPosition,};use futures::StreamExt;use gpui::{ Entity, Focusable, KeyBinding, KeybindingKeystroke, Keystroke, Modifiers, NoAction, Pixels, Task, prelude::*, size,};use indoc::indoc;use language::EditPredictionsMode;use language::{Buffer, CodeLabel};use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};use project::{Completion, CompletionResponse, CompletionSource};use std::{ ops::Range, path::PathBuf, rc::Rc, sync::{ Arc, atomic::{self, AtomicUsize}, },};use text::{Point, ToOffset};use ui::prelude::*; use crate::{ AcceptEditPrediction, CodeContextMenu, CompletionContext, CompletionProvider, EditPrediction, EditPredictionKeybindAction, EditPredictionKeybindSurface, MenuEditPredictionsPolicy, MultiBuffer, ShowCompletions, editor_tests::{init_test, update_test_language_settings}, test::{ build_editor, editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, },};use rpc::proto::PeerId;use workspace::CollaboratorId; struct EditorWithRightOccluders { editor: Entity<crate::Editor>, editor_width: Pixels, right_dock_width: Option<Pixels>, right_sidebar_width: Option<Pixels>,} impl Render for EditorWithRightOccluders { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { h_flex() .size_full() .child( div() .h_full() .w(self.editor_width) .overflow_hidden() .child(self.editor.clone()), ) .when_some(self.right_dock_width, |this, width| { this.child( div() .h_full() .w(width) .flex_shrink_0() .occlude() .debug_selector(|| "right_dock".into()), ) }) .when_some(self.right_sidebar_width, |this, width| { this.child( div() .h_full() .w(width) .flex_shrink_0() .occlude() .debug_selector(|| "right_sidebar".into()), ) }) }} async fn assert_edit_prediction_diff_popover_avoids_right_occluders( cx: &mut gpui::TestAppContext, right_dock_width: Option<Pixels>, right_sidebar_width: Option<Pixels>,) { init_test(cx, |_| {}); let editor_width = px(700.); let window_width = editor_width + right_dock_width.unwrap_or_default() + right_sidebar_width.unwrap_or_default(); let buffer = cx.update(|cx| MultiBuffer::build_simple("", cx)); let window = cx.add_window(|window, cx| { let editor = cx.new(|cx| build_editor(buffer, window, cx)); window.focus(&editor.focus_handle(cx), cx); EditorWithRightOccluders { editor, editor_width, right_dock_width, right_sidebar_width, } }); let editor = window .read_with(cx, |root, _| root.editor.clone()) .expect("test window should contain editor"); let mut cx = gpui::VisualTestContext::from_window(*window, cx); cx.simulate_resize(size(window_width, px(500.))); cx.run_until_parked(); let mut cx = EditorTestContext::for_editor_in(editor, &mut cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.update_editor(|editor, _, _| { editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never); }); cx.set_state("abcdefghijklmnopqrstuvwxyzabcdefghijklmnˇopqrstuvwxyzabcdef"); propose_edits(&provider, vec![(40..41, "REPLACEMENT")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); cx.editor(|editor, _, _| { assert!(!editor.edit_prediction_visible_in_cursor_popover(true)); assert!(matches!( editor .active_edit_prediction .as_ref() .map(|state| &state.completion), Some(EditPrediction::Edit { display_mode: crate::EditDisplayMode::DiffPopover, .. }) )); }); cx.cx.update(|window, cx| { window.refresh(); let _ = window.draw(cx); }); cx.editor(|editor, _, _| { assert!( editor.last_position_map.is_some(), "editor should have rendered a position map" ); }); let popover_bounds = cx .cx .debug_bounds("edit_prediction_diff_popover") .expect("diff popover should render"); for selector in ["right_dock", "right_sidebar"] { if let Some(occluder_bounds) = cx.cx.debug_bounds(selector) { assert!( !popover_bounds.intersects(&occluder_bounds), "diff popover {popover_bounds:?} should not overlap {selector} {occluder_bounds:?}" ); } }} #[gpui::test]async fn test_edit_prediction_diff_popover_avoids_right_sidebar(cx: &mut gpui::TestAppContext) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, None, Some(px(300.))).await;} #[gpui::test]async fn test_edit_prediction_diff_popover_avoids_right_dock(cx: &mut gpui::TestAppContext) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, Some(px(300.)), None).await;} #[gpui::test]async fn test_edit_prediction_diff_popover_avoids_right_dock_and_sidebar( cx: &mut gpui::TestAppContext,) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, Some(px(300.)), Some(px(300.))) .await;} #[gpui::test]async fn test_edit_prediction_insert(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let absolute_zero_celsius = ˇ;"); propose_edits(&provider, vec![(28..28, "-273.15")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "-273.15"); }); accept_completion(&mut cx); cx.assert_editor_state("let absolute_zero_celsius = -273.15ˇ;")} #[gpui::test]async fn test_edit_prediction_cursor_position_inside_insertion(cx: &mut gpui::TestAppContext) { init_test(cx, |_| { eprintln!(""); }); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Buffer: "fn foo() {}" - we'll insert text and position cursor inside the insertion cx.set_state("fn foo() ˇ{}"); // Insert "bar()" at offset 9, with cursor at offset 2 within the insertion (after "ba") // This tests the case where cursor is inside newly inserted text propose_edits_with_cursor_position_in_insertion( &provider, vec![(9..9, "bar()")], 9, // anchor at the insertion point 2, // offset 2 within "bar()" puts cursor after "ba" &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "bar()"); }); accept_completion(&mut cx); // Cursor should be inside the inserted text at "baˇr()" cx.assert_editor_state("fn foo() baˇr(){}");} #[gpui::test]async fn test_edit_prediction_cursor_position_outside_edit(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Buffer: "let x = ;" with cursor before semicolon - we'll insert "42" and position cursor elsewhere cx.set_state("let x = ˇ;"); // Insert "42" at offset 8, but set cursor_position to offset 4 (the 'x') // This tests that cursor moves to the predicted position, not the end of the edit propose_edits_with_cursor_position( &provider, vec![(8..8, "42")], Some(4), // cursor at offset 4 (the 'x'), NOT at the edit location &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "42"); }); accept_completion(&mut cx); // Cursor should be at offset 4 (the 'x'), not at the end of the inserted "42" cx.assert_editor_state("let ˇx = 42;");} #[gpui::test]async fn test_edit_prediction_cursor_position_fallback(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let x = ˇ;"); // Propose an edit without a cursor position - should fall back to end of edit propose_edits(&provider, vec![(8..8, "42")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); accept_completion(&mut cx); // Cursor should be at the end of the inserted text (default behavior) cx.assert_editor_state("let x = 42ˇ;")} #[gpui::test]async fn test_edit_prediction_modification(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let pi = ˇ\"foo\";"); propose_edits(&provider, vec![(9..14, "3.14159")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "3.14159"); }); accept_completion(&mut cx); cx.assert_editor_state("let pi = 3.14159ˇ;")} #[gpui::test]async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Cursor is 2+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line "}); propose_edits( &provider, vec![(Point::new(4, 3)..Point::new(4, 3), " 4")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(4, 3)); }); // When accepting, cursor is moved to the proposed location accept_completion(&mut cx); cx.assert_editor_state(indoc! {" line 0 line 1 line 2 line 3 linˇe "}); // Cursor is 2+ lines below the proposed edit cx.set_state(indoc! {" line 0 line line 2 line 3 line ˇ4 "}); propose_edits( &provider, vec![(Point::new(1, 3)..Point::new(1, 3), " 1")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(1, 3)); }); // When accepting, cursor is moved to the proposed location accept_completion(&mut cx); cx.assert_editor_state(indoc! {" line 0 linˇe line 2 line 3 line 4 "});} #[gpui::test]async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Cursor is 3+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line 4 line "}); let edit_location = Point::new(5, 3); propose_edits( &provider, vec![(edit_location..edit_location, " 5")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *towards* the completion, it stays active cx.set_selections_state(indoc! {" line 0 line 1 line ˇ2 line 3 line 4 line "}); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *away* from the completion, it is discarded cx.set_selections_state(indoc! {" line ˇ0 line 1 line 2 line 3 line 4 line "}); cx.editor(|editor, _, _| { assert!(editor.active_edit_prediction.is_none()); }); // Cursor is 3+ lines below the proposed edit cx.set_state(indoc! {" line line 1 line 2 line 3 line ˇ4 line 5 "}); let edit_location = Point::new(0, 3); propose_edits( &provider, vec![(edit_location..edit_location, " 0")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *towards* the completion, it stays active cx.set_selections_state(indoc! {" line line 1 line 2 line ˇ3 line 4 line 5 "}); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *away* from the completion, it is discarded cx.set_selections_state(indoc! {" line line 1 line 2 line 3 line 4 line ˇ5 "}); cx.editor(|editor, _, _| { assert!(editor.active_edit_prediction.is_none()); });} #[gpui::test]async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeNonZedEditPredictionDelegate::default()); assign_editor_completion_provider_non_zed(provider.clone(), &mut cx); // Cursor is 2+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line "}); propose_edits_non_zed( &provider, vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],Lines 1–500 of 1903. Load more
use edit_prediction_types::{ EditPredictionDelegate, EditPredictionIconSet, EditPredictionRequestTrigger, PredictedCursorPosition, }; use futures::StreamExt; use gpui::{ Entity, Focusable, KeyBinding, KeybindingKeystroke, Keystroke, Modifiers, NoAction, Pixels, Task, prelude::*, size, }; use indoc::indoc; use language::EditPredictionsMode; use language::{Buffer, CodeLabel}; use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint}; use project::{Completion, CompletionResponse, CompletionSource}; use std::{ ops::Range, path::PathBuf, rc::Rc, sync::{ Arc, atomic::{self, AtomicUsize}, }, }; use text::{Point, ToOffset}; use ui::prelude::*; use crate::{ AcceptEditPrediction, CodeContextMenu, CompletionContext, CompletionProvider, EditPrediction, EditPredictionKeybindAction, EditPredictionKeybindSurface, MenuEditPredictionsPolicy, MultiBuffer, ShowCompletions, editor_tests::{init_test, update_test_language_settings}, test::{ build_editor, editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, }, }; use rpc::proto::PeerId; use workspace::CollaboratorId; struct EditorWithRightOccluders { editor: Entity<crate::Editor>, editor_width: Pixels, right_dock_width: Option<Pixels>, right_sidebar_width: Option<Pixels>, } impl Render for EditorWithRightOccluders { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { h_flex() .size_full() .child( div() .h_full() .w(self.editor_width) .overflow_hidden() .child(self.editor.clone()), ) .when_some(self.right_dock_width, |this, width| { this.child( div() .h_full() .w(width) .flex_shrink_0() .occlude() .debug_selector(|| "right_dock".into()), ) }) .when_some(self.right_sidebar_width, |this, width| { this.child( div() .h_full() .w(width) .flex_shrink_0() .occlude() .debug_selector(|| "right_sidebar".into()), ) }) } } async fn assert_edit_prediction_diff_popover_avoids_right_occluders( cx: &mut gpui::TestAppContext, right_dock_width: Option<Pixels>, right_sidebar_width: Option<Pixels>, ) { init_test(cx, |_| {}); let editor_width = px(700.); let window_width = editor_width + right_dock_width.unwrap_or_default() + right_sidebar_width.unwrap_or_default(); let buffer = cx.update(|cx| MultiBuffer::build_simple("", cx)); let window = cx.add_window(|window, cx| { let editor = cx.new(|cx| build_editor(buffer, window, cx)); window.focus(&editor.focus_handle(cx), cx); EditorWithRightOccluders { editor, editor_width, right_dock_width, right_sidebar_width, } }); let editor = window .read_with(cx, |root, _| root.editor.clone()) .expect("test window should contain editor"); let mut cx = gpui::VisualTestContext::from_window(*window, cx); cx.simulate_resize(size(window_width, px(500.))); cx.run_until_parked(); let mut cx = EditorTestContext::for_editor_in(editor, &mut cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.update_editor(|editor, _, _| { editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never); }); cx.set_state("abcdefghijklmnopqrstuvwxyzabcdefghijklmnˇopqrstuvwxyzabcdef"); propose_edits(&provider, vec![(40..41, "REPLACEMENT")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); cx.editor(|editor, _, _| { assert!(!editor.edit_prediction_visible_in_cursor_popover(true)); assert!(matches!( editor .active_edit_prediction .as_ref() .map(|state| &state.completion), Some(EditPrediction::Edit { display_mode: crate::EditDisplayMode::DiffPopover, .. }) )); }); cx.cx.update(|window, cx| { window.refresh(); let _ = window.draw(cx); }); cx.editor(|editor, _, _| { assert!( editor.last_position_map.is_some(), "editor should have rendered a position map" ); }); let popover_bounds = cx .cx .debug_bounds("edit_prediction_diff_popover") .expect("diff popover should render"); for selector in ["right_dock", "right_sidebar"] { if let Some(occluder_bounds) = cx.cx.debug_bounds(selector) { assert!( !popover_bounds.intersects(&occluder_bounds), "diff popover {popover_bounds:?} should not overlap {selector} {occluder_bounds:?}" ); } } } #[gpui::test] async fn test_edit_prediction_diff_popover_avoids_right_sidebar(cx: &mut gpui::TestAppContext) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, None, Some(px(300.))).await; } #[gpui::test] async fn test_edit_prediction_diff_popover_avoids_right_dock(cx: &mut gpui::TestAppContext) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, Some(px(300.)), None).await; } #[gpui::test] async fn test_edit_prediction_diff_popover_avoids_right_dock_and_sidebar( cx: &mut gpui::TestAppContext, ) { assert_edit_prediction_diff_popover_avoids_right_occluders(cx, Some(px(300.)), Some(px(300.))) .await; } #[gpui::test] async fn test_edit_prediction_insert(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let absolute_zero_celsius = ˇ;"); propose_edits(&provider, vec![(28..28, "-273.15")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "-273.15"); }); accept_completion(&mut cx); cx.assert_editor_state("let absolute_zero_celsius = -273.15ˇ;") } #[gpui::test] async fn test_edit_prediction_cursor_position_inside_insertion(cx: &mut gpui::TestAppContext) { init_test(cx, |_| { eprintln!(""); }); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Buffer: "fn foo() {}" - we'll insert text and position cursor inside the insertion cx.set_state("fn foo() ˇ{}"); // Insert "bar()" at offset 9, with cursor at offset 2 within the insertion (after "ba") // This tests the case where cursor is inside newly inserted text propose_edits_with_cursor_position_in_insertion( &provider, vec![(9..9, "bar()")], 9, // anchor at the insertion point 2, // offset 2 within "bar()" puts cursor after "ba" &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "bar()"); }); accept_completion(&mut cx); // Cursor should be inside the inserted text at "baˇr()" cx.assert_editor_state("fn foo() baˇr(){}"); } #[gpui::test] async fn test_edit_prediction_cursor_position_outside_edit(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Buffer: "let x = ;" with cursor before semicolon - we'll insert "42" and position cursor elsewhere cx.set_state("let x = ˇ;"); // Insert "42" at offset 8, but set cursor_position to offset 4 (the 'x') // This tests that cursor moves to the predicted position, not the end of the edit propose_edits_with_cursor_position( &provider, vec![(8..8, "42")], Some(4), // cursor at offset 4 (the 'x'), NOT at the edit location &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "42"); }); accept_completion(&mut cx); // Cursor should be at offset 4 (the 'x'), not at the end of the inserted "42" cx.assert_editor_state("let ˇx = 42;"); } #[gpui::test] async fn test_edit_prediction_cursor_position_fallback(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let x = ˇ;"); // Propose an edit without a cursor position - should fall back to end of edit propose_edits(&provider, vec![(8..8, "42")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); accept_completion(&mut cx); // Cursor should be at the end of the inserted text (default behavior) cx.assert_editor_state("let x = 42ˇ;") } #[gpui::test] async fn test_edit_prediction_modification(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); cx.set_state("let pi = ˇ\"foo\";"); propose_edits(&provider, vec![(9..14, "3.14159")], &mut cx); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_edit_completion(&mut cx, |_, edits| { assert_eq!(edits.len(), 1); assert_eq!(edits[0].1.as_ref(), "3.14159"); }); accept_completion(&mut cx); cx.assert_editor_state("let pi = 3.14159ˇ;") } #[gpui::test] async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Cursor is 2+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line "}); propose_edits( &provider, vec![(Point::new(4, 3)..Point::new(4, 3), " 4")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(4, 3)); }); // When accepting, cursor is moved to the proposed location accept_completion(&mut cx); cx.assert_editor_state(indoc! {" line 0 line 1 line 2 line 3 linˇe "}); // Cursor is 2+ lines below the proposed edit cx.set_state(indoc! {" line 0 line line 2 line 3 line ˇ4 "}); propose_edits( &provider, vec![(Point::new(1, 3)..Point::new(1, 3), " 1")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), Point::new(1, 3)); }); // When accepting, cursor is moved to the proposed location accept_completion(&mut cx); cx.assert_editor_state(indoc! {" line 0 linˇe line 2 line 3 line 4 "}); } #[gpui::test] async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeEditPredictionDelegate::default()); assign_editor_completion_provider(provider.clone(), &mut cx); // Cursor is 3+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line 4 line "}); let edit_location = Point::new(5, 3); propose_edits( &provider, vec![(edit_location..edit_location, " 5")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *towards* the completion, it stays active cx.set_selections_state(indoc! {" line 0 line 1 line ˇ2 line 3 line 4 line "}); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *away* from the completion, it is discarded cx.set_selections_state(indoc! {" line ˇ0 line 1 line 2 line 3 line 4 line "}); cx.editor(|editor, _, _| { assert!(editor.active_edit_prediction.is_none()); }); // Cursor is 3+ lines below the proposed edit cx.set_state(indoc! {" line line 1 line 2 line 3 line ˇ4 line 5 "}); let edit_location = Point::new(0, 3); propose_edits( &provider, vec![(edit_location..edit_location, " 0")], &mut cx, ); cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *towards* the completion, it stays active cx.set_selections_state(indoc! {" line line 1 line 2 line ˇ3 line 4 line 5 "}); assert_editor_active_move_completion(&mut cx, |snapshot, move_target| { assert_eq!(move_target.to_point(&snapshot), edit_location); }); // If we move *away* from the completion, it is discarded cx.set_selections_state(indoc! {" line line 1 line 2 line 3 line 4 line ˇ5 "}); cx.editor(|editor, _, _| { assert!(editor.active_edit_prediction.is_none()); }); } #[gpui::test] async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; let provider = cx.new(|_| FakeNonZedEditPredictionDelegate::default()); assign_editor_completion_provider_non_zed(provider.clone(), &mut cx); // Cursor is 2+ lines above the proposed edit cx.set_state(indoc! {" line 0 line ˇ1 line 2 line 3 line "}); propose_edits_non_zed( &provider, vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],