1903 lines · 63.9 KB · Rust
RawDownload
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.