edit_prediction_tests.rs

   1use edit_prediction_types::{
   2    EditPredictionDelegate, EditPredictionIconSet, PredictedCursorPosition,
   3};
   4use gpui::{
   5    Entity, KeyBinding, KeybindingKeystroke, Keystroke, Modifiers, NoAction, Task, prelude::*,
   6};
   7use indoc::indoc;
   8use language::EditPredictionsMode;
   9use language::{Buffer, CodeLabel};
  10use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
  11use project::{Completion, CompletionResponse, CompletionSource};
  12use std::{
  13    ops::Range,
  14    rc::Rc,
  15    sync::{
  16        Arc,
  17        atomic::{self, AtomicUsize},
  18    },
  19};
  20use text::{Point, ToOffset};
  21use ui::prelude::*;
  22
  23use crate::{
  24    AcceptEditPrediction, CompletionContext, CompletionProvider, EditPrediction,
  25    EditPredictionKeybindAction, EditPredictionKeybindSurface, MenuEditPredictionsPolicy,
  26    ShowCompletions,
  27    editor_tests::{init_test, update_test_language_settings},
  28    test::editor_test_context::EditorTestContext,
  29};
  30use rpc::proto::PeerId;
  31use workspace::CollaboratorId;
  32
  33#[gpui::test]
  34async fn test_edit_prediction_insert(cx: &mut gpui::TestAppContext) {
  35    init_test(cx, |_| {});
  36
  37    let mut cx = EditorTestContext::new(cx).await;
  38    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
  39    assign_editor_completion_provider(provider.clone(), &mut cx);
  40    cx.set_state("let absolute_zero_celsius = ˇ;");
  41
  42    propose_edits(&provider, vec![(28..28, "-273.15")], &mut cx);
  43    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
  44
  45    assert_editor_active_edit_completion(&mut cx, |_, edits| {
  46        assert_eq!(edits.len(), 1);
  47        assert_eq!(edits[0].1.as_ref(), "-273.15");
  48    });
  49
  50    accept_completion(&mut cx);
  51
  52    cx.assert_editor_state("let absolute_zero_celsius = -273.15ˇ;")
  53}
  54
  55#[gpui::test]
  56async fn test_edit_prediction_cursor_position_inside_insertion(cx: &mut gpui::TestAppContext) {
  57    init_test(cx, |_| {
  58        eprintln!("");
  59    });
  60
  61    let mut cx = EditorTestContext::new(cx).await;
  62    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
  63
  64    assign_editor_completion_provider(provider.clone(), &mut cx);
  65    // Buffer: "fn foo() {}" - we'll insert text and position cursor inside the insertion
  66    cx.set_state("fn foo() ˇ{}");
  67
  68    // Insert "bar()" at offset 9, with cursor at offset 2 within the insertion (after "ba")
  69    // This tests the case where cursor is inside newly inserted text
  70    propose_edits_with_cursor_position_in_insertion(
  71        &provider,
  72        vec![(9..9, "bar()")],
  73        9, // anchor at the insertion point
  74        2, // offset 2 within "bar()" puts cursor after "ba"
  75        &mut cx,
  76    );
  77    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
  78
  79    assert_editor_active_edit_completion(&mut cx, |_, edits| {
  80        assert_eq!(edits.len(), 1);
  81        assert_eq!(edits[0].1.as_ref(), "bar()");
  82    });
  83
  84    accept_completion(&mut cx);
  85
  86    // Cursor should be inside the inserted text at "baˇr()"
  87    cx.assert_editor_state("fn foo() baˇr(){}");
  88}
  89
  90#[gpui::test]
  91async fn test_edit_prediction_cursor_position_outside_edit(cx: &mut gpui::TestAppContext) {
  92    init_test(cx, |_| {});
  93
  94    let mut cx = EditorTestContext::new(cx).await;
  95    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
  96    assign_editor_completion_provider(provider.clone(), &mut cx);
  97    // Buffer: "let x = ;" with cursor before semicolon - we'll insert "42" and position cursor elsewhere
  98    cx.set_state("let x = ˇ;");
  99
 100    // Insert "42" at offset 8, but set cursor_position to offset 4 (the 'x')
 101    // This tests that cursor moves to the predicted position, not the end of the edit
 102    propose_edits_with_cursor_position(
 103        &provider,
 104        vec![(8..8, "42")],
 105        Some(4), // cursor at offset 4 (the 'x'), NOT at the edit location
 106        &mut cx,
 107    );
 108    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 109
 110    assert_editor_active_edit_completion(&mut cx, |_, edits| {
 111        assert_eq!(edits.len(), 1);
 112        assert_eq!(edits[0].1.as_ref(), "42");
 113    });
 114
 115    accept_completion(&mut cx);
 116
 117    // Cursor should be at offset 4 (the 'x'), not at the end of the inserted "42"
 118    cx.assert_editor_state("let ˇx = 42;");
 119}
 120
 121#[gpui::test]
 122async fn test_edit_prediction_cursor_position_fallback(cx: &mut gpui::TestAppContext) {
 123    init_test(cx, |_| {});
 124
 125    let mut cx = EditorTestContext::new(cx).await;
 126    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 127    assign_editor_completion_provider(provider.clone(), &mut cx);
 128    cx.set_state("let x = ˇ;");
 129
 130    // Propose an edit without a cursor position - should fall back to end of edit
 131    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 132    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 133
 134    accept_completion(&mut cx);
 135
 136    // Cursor should be at the end of the inserted text (default behavior)
 137    cx.assert_editor_state("let x = 42ˇ;")
 138}
 139
 140#[gpui::test]
 141async fn test_edit_prediction_modification(cx: &mut gpui::TestAppContext) {
 142    init_test(cx, |_| {});
 143
 144    let mut cx = EditorTestContext::new(cx).await;
 145    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 146    assign_editor_completion_provider(provider.clone(), &mut cx);
 147    cx.set_state("let pi = ˇ\"foo\";");
 148
 149    propose_edits(&provider, vec![(9..14, "3.14159")], &mut cx);
 150    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 151
 152    assert_editor_active_edit_completion(&mut cx, |_, edits| {
 153        assert_eq!(edits.len(), 1);
 154        assert_eq!(edits[0].1.as_ref(), "3.14159");
 155    });
 156
 157    accept_completion(&mut cx);
 158
 159    cx.assert_editor_state("let pi = 3.14159ˇ;")
 160}
 161
 162#[gpui::test]
 163async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) {
 164    init_test(cx, |_| {});
 165
 166    let mut cx = EditorTestContext::new(cx).await;
 167    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 168    assign_editor_completion_provider(provider.clone(), &mut cx);
 169
 170    // Cursor is 2+ lines above the proposed edit
 171    cx.set_state(indoc! {"
 172        line 0
 173        line ˇ1
 174        line 2
 175        line 3
 176        line
 177    "});
 178
 179    propose_edits(
 180        &provider,
 181        vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],
 182        &mut cx,
 183    );
 184
 185    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 186    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 187        assert_eq!(move_target.to_point(&snapshot), Point::new(4, 3));
 188    });
 189
 190    // When accepting, cursor is moved to the proposed location
 191    accept_completion(&mut cx);
 192    cx.assert_editor_state(indoc! {"
 193        line 0
 194        line 1
 195        line 2
 196        line 3
 197        linˇe
 198    "});
 199
 200    // Cursor is 2+ lines below the proposed edit
 201    cx.set_state(indoc! {"
 202        line 0
 203        line
 204        line 2
 205        line 3
 206        line ˇ4
 207    "});
 208
 209    propose_edits(
 210        &provider,
 211        vec![(Point::new(1, 3)..Point::new(1, 3), " 1")],
 212        &mut cx,
 213    );
 214
 215    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 216    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 217        assert_eq!(move_target.to_point(&snapshot), Point::new(1, 3));
 218    });
 219
 220    // When accepting, cursor is moved to the proposed location
 221    accept_completion(&mut cx);
 222    cx.assert_editor_state(indoc! {"
 223        line 0
 224        linˇe
 225        line 2
 226        line 3
 227        line 4
 228    "});
 229}
 230
 231#[gpui::test]
 232async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) {
 233    init_test(cx, |_| {});
 234
 235    let mut cx = EditorTestContext::new(cx).await;
 236    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 237    assign_editor_completion_provider(provider.clone(), &mut cx);
 238
 239    // Cursor is 3+ lines above the proposed edit
 240    cx.set_state(indoc! {"
 241        line 0
 242        line ˇ1
 243        line 2
 244        line 3
 245        line 4
 246        line
 247    "});
 248    let edit_location = Point::new(5, 3);
 249
 250    propose_edits(
 251        &provider,
 252        vec![(edit_location..edit_location, " 5")],
 253        &mut cx,
 254    );
 255
 256    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 257    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 258        assert_eq!(move_target.to_point(&snapshot), edit_location);
 259    });
 260
 261    // If we move *towards* the completion, it stays active
 262    cx.set_selections_state(indoc! {"
 263        line 0
 264        line 1
 265        line ˇ2
 266        line 3
 267        line 4
 268        line
 269    "});
 270    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 271        assert_eq!(move_target.to_point(&snapshot), edit_location);
 272    });
 273
 274    // If we move *away* from the completion, it is discarded
 275    cx.set_selections_state(indoc! {"
 276        line ˇ0
 277        line 1
 278        line 2
 279        line 3
 280        line 4
 281        line
 282    "});
 283    cx.editor(|editor, _, _| {
 284        assert!(editor.active_edit_prediction.is_none());
 285    });
 286
 287    // Cursor is 3+ lines below the proposed edit
 288    cx.set_state(indoc! {"
 289        line
 290        line 1
 291        line 2
 292        line 3
 293        line ˇ4
 294        line 5
 295    "});
 296    let edit_location = Point::new(0, 3);
 297
 298    propose_edits(
 299        &provider,
 300        vec![(edit_location..edit_location, " 0")],
 301        &mut cx,
 302    );
 303
 304    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 305    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 306        assert_eq!(move_target.to_point(&snapshot), edit_location);
 307    });
 308
 309    // If we move *towards* the completion, it stays active
 310    cx.set_selections_state(indoc! {"
 311        line
 312        line 1
 313        line 2
 314        line ˇ3
 315        line 4
 316        line 5
 317    "});
 318    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
 319        assert_eq!(move_target.to_point(&snapshot), edit_location);
 320    });
 321
 322    // If we move *away* from the completion, it is discarded
 323    cx.set_selections_state(indoc! {"
 324        line
 325        line 1
 326        line 2
 327        line 3
 328        line 4
 329        line ˇ5
 330    "});
 331    cx.editor(|editor, _, _| {
 332        assert!(editor.active_edit_prediction.is_none());
 333    });
 334}
 335
 336#[gpui::test]
 337async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui::TestAppContext) {
 338    init_test(cx, |_| {});
 339
 340    let mut cx = EditorTestContext::new(cx).await;
 341    let provider = cx.new(|_| FakeNonZedEditPredictionDelegate::default());
 342    assign_editor_completion_provider_non_zed(provider.clone(), &mut cx);
 343
 344    // Cursor is 2+ lines above the proposed edit
 345    cx.set_state(indoc! {"
 346        line 0
 347        line ˇ1
 348        line 2
 349        line 3
 350        line
 351    "});
 352
 353    propose_edits_non_zed(
 354        &provider,
 355        vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],
 356        &mut cx,
 357    );
 358
 359    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 360
 361    // For non-Zed providers, there should be no move completion (jump functionality disabled)
 362    cx.editor(|editor, _, _| {
 363        if let Some(completion_state) = &editor.active_edit_prediction {
 364            // Should be an Edit prediction, not a Move prediction
 365            match &completion_state.completion {
 366                EditPrediction::Edit { .. } => {
 367                    // This is expected for non-Zed providers
 368                }
 369                EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => {
 370                    panic!(
 371                        "Non-Zed providers should not show Move predictions (jump functionality)"
 372                    );
 373                }
 374            }
 375        }
 376    });
 377}
 378
 379#[gpui::test]
 380async fn test_edit_prediction_refresh_suppressed_while_following(cx: &mut gpui::TestAppContext) {
 381    init_test(cx, |_| {});
 382
 383    let mut cx = EditorTestContext::new(cx).await;
 384    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 385    assign_editor_completion_provider(provider.clone(), &mut cx);
 386    cx.set_state("let x = ˇ;");
 387
 388    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 389
 390    cx.update_editor(|editor, window, cx| {
 391        editor.refresh_edit_prediction(false, false, window, cx);
 392        editor.update_visible_edit_prediction(window, cx);
 393    });
 394
 395    assert_eq!(
 396        provider.read_with(&cx.cx, |provider, _| {
 397            provider.refresh_count.load(atomic::Ordering::SeqCst)
 398        }),
 399        1
 400    );
 401    cx.editor(|editor, _, _| {
 402        assert!(editor.active_edit_prediction.is_some());
 403    });
 404
 405    cx.update_editor(|editor, window, cx| {
 406        editor.leader_id = Some(CollaboratorId::PeerId(PeerId::default()));
 407        editor.refresh_edit_prediction(false, false, window, cx);
 408    });
 409
 410    assert_eq!(
 411        provider.read_with(&cx.cx, |provider, _| {
 412            provider.refresh_count.load(atomic::Ordering::SeqCst)
 413        }),
 414        1
 415    );
 416    cx.editor(|editor, _, _| {
 417        assert!(editor.active_edit_prediction.is_none());
 418    });
 419
 420    cx.update_editor(|editor, window, cx| {
 421        editor.leader_id = None;
 422        editor.refresh_edit_prediction(false, false, window, cx);
 423    });
 424
 425    assert_eq!(
 426        provider.read_with(&cx.cx, |provider, _| {
 427            provider.refresh_count.load(atomic::Ordering::SeqCst)
 428        }),
 429        2
 430    );
 431}
 432
 433#[gpui::test]
 434async fn test_edit_prediction_preview_cleanup_on_toggle_off(cx: &mut gpui::TestAppContext) {
 435    init_test(cx, |_| {});
 436
 437    // Bind `ctrl-shift-a` to accept the provided edit prediction. The actual key
 438    // binding here doesn't matter, we simply need to confirm that holding the
 439    // binding's modifiers triggers the edit prediction preview.
 440    cx.update(|cx| cx.bind_keys([KeyBinding::new("ctrl-shift-a", AcceptEditPrediction, None)]));
 441
 442    let mut cx = EditorTestContext::new(cx).await;
 443    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 444    assign_editor_completion_provider(provider.clone(), &mut cx);
 445    cx.set_state("let x = ˇ;");
 446
 447    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 448    cx.update_editor(|editor, window, cx| {
 449        editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::ByProvider);
 450        editor.update_visible_edit_prediction(window, cx)
 451    });
 452
 453    cx.editor(|editor, _, _| {
 454        assert!(editor.has_active_edit_prediction());
 455    });
 456
 457    // Simulate pressing the modifiers for `AcceptEditPrediction`, namely
 458    // `ctrl-shift`, so that we can confirm that the edit prediction preview is
 459    // activated.
 460    let modifiers = Modifiers::control_shift();
 461    cx.simulate_modifiers_change(modifiers);
 462    cx.run_until_parked();
 463
 464    cx.editor(|editor, _, _| {
 465        assert!(editor.edit_prediction_preview_is_active());
 466    });
 467
 468    // Disable showing edit predictions without issuing a new modifiers changed
 469    // event, to confirm that the edit prediction preview is still active.
 470    cx.update_editor(|editor, window, cx| {
 471        editor.set_show_edit_predictions(Some(false), window, cx);
 472    });
 473
 474    cx.editor(|editor, _, _| {
 475        assert!(!editor.has_active_edit_prediction());
 476        assert!(editor.edit_prediction_preview_is_active());
 477    });
 478
 479    // Now release the modifiers
 480    // Simulate releasing all modifiers, ensuring that even with edit prediction
 481    // disabled, the edit prediction preview is cleaned up.
 482    cx.simulate_modifiers_change(Modifiers::none());
 483    cx.run_until_parked();
 484
 485    cx.editor(|editor, _, _| {
 486        assert!(!editor.edit_prediction_preview_is_active());
 487    });
 488}
 489
 490#[gpui::test]
 491async fn test_edit_prediction_preview_activates_when_prediction_arrives_with_modifier_held(
 492    cx: &mut gpui::TestAppContext,
 493) {
 494    init_test(cx, |_| {});
 495    load_default_keymap(cx);
 496    update_test_language_settings(cx, &|settings| {
 497        settings.edit_predictions.get_or_insert_default().mode = Some(EditPredictionsMode::Subtle);
 498    });
 499
 500    let mut cx = EditorTestContext::new(cx).await;
 501    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 502    assign_editor_completion_provider(provider.clone(), &mut cx);
 503    cx.set_state("let x = ˇ;");
 504
 505    cx.editor(|editor, _, _| {
 506        assert!(!editor.has_active_edit_prediction());
 507        assert!(!editor.edit_prediction_preview_is_active());
 508    });
 509
 510    let preview_modifiers = cx.update_editor(|editor, window, cx| {
 511        *editor
 512            .preview_edit_prediction_keystroke(window, cx)
 513            .unwrap()
 514            .modifiers()
 515    });
 516
 517    cx.simulate_modifiers_change(preview_modifiers);
 518    cx.run_until_parked();
 519
 520    cx.editor(|editor, _, _| {
 521        assert!(!editor.has_active_edit_prediction());
 522        assert!(editor.edit_prediction_preview_is_active());
 523    });
 524
 525    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 526    cx.update_editor(|editor, window, cx| {
 527        editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::ByProvider);
 528        editor.update_visible_edit_prediction(window, cx)
 529    });
 530
 531    cx.editor(|editor, _, _| {
 532        assert!(editor.has_active_edit_prediction());
 533        assert!(
 534            editor.edit_prediction_preview_is_active(),
 535            "prediction preview should activate immediately when the prediction arrives while the preview modifier is still held",
 536        );
 537    });
 538}
 539
 540fn load_default_keymap(cx: &mut gpui::TestAppContext) {
 541    cx.update(|cx| {
 542        cx.bind_keys(
 543            settings::KeymapFile::load_asset_allow_partial_failure(
 544                settings::DEFAULT_KEYMAP_PATH,
 545                cx,
 546            )
 547            .expect("failed to load default keymap"),
 548        );
 549    });
 550}
 551
 552#[gpui::test]
 553async fn test_inline_edit_prediction_keybind_selection_cases(cx: &mut gpui::TestAppContext) {
 554    enum InlineKeybindState {
 555        Normal,
 556        ShowingCompletions,
 557        InLeadingWhitespace,
 558        ShowingCompletionsAndLeadingWhitespace,
 559    }
 560
 561    enum ExpectedKeystroke {
 562        DefaultAccept,
 563        DefaultPreview,
 564        Literal(&'static str),
 565    }
 566
 567    struct InlineKeybindCase {
 568        name: &'static str,
 569        use_default_keymap: bool,
 570        mode: EditPredictionsMode,
 571        extra_bindings: Vec<KeyBinding>,
 572        state: InlineKeybindState,
 573        expected_accept_keystroke: ExpectedKeystroke,
 574        expected_preview_keystroke: ExpectedKeystroke,
 575        expected_displayed_keystroke: ExpectedKeystroke,
 576    }
 577
 578    init_test(cx, |_| {});
 579    load_default_keymap(cx);
 580    let mut default_cx = EditorTestContext::new(cx).await;
 581    let provider = default_cx.new(|_| FakeEditPredictionDelegate::default());
 582    assign_editor_completion_provider(provider.clone(), &mut default_cx);
 583    default_cx.set_state("let x = ˇ;");
 584    propose_edits(&provider, vec![(8..8, "42")], &mut default_cx);
 585    default_cx
 586        .update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 587
 588    let (default_accept_keystroke, default_preview_keystroke) =
 589        default_cx.update_editor(|editor, window, cx| {
 590            let keybind_display = editor.edit_prediction_keybind_display(
 591                EditPredictionKeybindSurface::Inline,
 592                window,
 593                cx,
 594            );
 595            let accept_keystroke = keybind_display
 596                .accept_keystroke
 597                .as_ref()
 598                .expect("default inline edit prediction should have an accept binding")
 599                .clone();
 600            let preview_keystroke = keybind_display
 601                .preview_keystroke
 602                .as_ref()
 603                .expect("default inline edit prediction should have a preview binding")
 604                .clone();
 605            (accept_keystroke, preview_keystroke)
 606        });
 607
 608    let cases = [
 609        InlineKeybindCase {
 610            name: "default setup prefers tab over alt-tab for accept",
 611            use_default_keymap: true,
 612            mode: EditPredictionsMode::Eager,
 613            extra_bindings: Vec::new(),
 614            state: InlineKeybindState::Normal,
 615            expected_accept_keystroke: ExpectedKeystroke::DefaultAccept,
 616            expected_preview_keystroke: ExpectedKeystroke::DefaultPreview,
 617            expected_displayed_keystroke: ExpectedKeystroke::DefaultAccept,
 618        },
 619        InlineKeybindCase {
 620            name: "subtle mode displays preview binding inline",
 621            use_default_keymap: true,
 622            mode: EditPredictionsMode::Subtle,
 623            extra_bindings: Vec::new(),
 624            state: InlineKeybindState::Normal,
 625            expected_accept_keystroke: ExpectedKeystroke::DefaultPreview,
 626            expected_preview_keystroke: ExpectedKeystroke::DefaultPreview,
 627            expected_displayed_keystroke: ExpectedKeystroke::DefaultPreview,
 628        },
 629        InlineKeybindCase {
 630            name: "removing default tab binding still displays tab",
 631            use_default_keymap: true,
 632            mode: EditPredictionsMode::Eager,
 633            extra_bindings: vec![KeyBinding::new(
 634                "tab",
 635                NoAction,
 636                Some("Editor && edit_prediction && edit_prediction_mode == eager"),
 637            )],
 638            state: InlineKeybindState::Normal,
 639            expected_accept_keystroke: ExpectedKeystroke::DefaultPreview,
 640            expected_preview_keystroke: ExpectedKeystroke::DefaultPreview,
 641            expected_displayed_keystroke: ExpectedKeystroke::DefaultPreview,
 642        },
 643        InlineKeybindCase {
 644            name: "custom-only rebound accept key uses replacement key",
 645            use_default_keymap: true,
 646            mode: EditPredictionsMode::Eager,
 647            extra_bindings: vec![KeyBinding::new(
 648                "ctrl-enter",
 649                AcceptEditPrediction,
 650                Some("Editor && edit_prediction"),
 651            )],
 652            state: InlineKeybindState::Normal,
 653            expected_accept_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 654            expected_preview_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 655            expected_displayed_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 656        },
 657        InlineKeybindCase {
 658            name: "showing completions restores conflict-context binding",
 659            use_default_keymap: true,
 660            mode: EditPredictionsMode::Eager,
 661            extra_bindings: vec![KeyBinding::new(
 662                "ctrl-enter",
 663                AcceptEditPrediction,
 664                Some("Editor && edit_prediction && showing_completions"),
 665            )],
 666            state: InlineKeybindState::ShowingCompletions,
 667            expected_accept_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 668            expected_preview_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 669            expected_displayed_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 670        },
 671        InlineKeybindCase {
 672            name: "leading whitespace restores conflict-context binding",
 673            use_default_keymap: false,
 674            mode: EditPredictionsMode::Eager,
 675            extra_bindings: vec![KeyBinding::new(
 676                "ctrl-enter",
 677                AcceptEditPrediction,
 678                Some("Editor && edit_prediction && in_leading_whitespace"),
 679            )],
 680            state: InlineKeybindState::InLeadingWhitespace,
 681            expected_accept_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 682            expected_preview_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 683            expected_displayed_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 684        },
 685        InlineKeybindCase {
 686            name: "showing completions and leading whitespace restore combined conflict binding",
 687            use_default_keymap: false,
 688            mode: EditPredictionsMode::Eager,
 689            extra_bindings: vec![KeyBinding::new(
 690                "ctrl-enter",
 691                AcceptEditPrediction,
 692                Some("Editor && edit_prediction && showing_completions && in_leading_whitespace"),
 693            )],
 694            state: InlineKeybindState::ShowingCompletionsAndLeadingWhitespace,
 695            expected_accept_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 696            expected_preview_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 697            expected_displayed_keystroke: ExpectedKeystroke::Literal("ctrl-enter"),
 698        },
 699    ];
 700
 701    for case in cases {
 702        init_test(cx, |_| {});
 703        if case.use_default_keymap {
 704            load_default_keymap(cx);
 705        }
 706        update_test_language_settings(cx, &|settings| {
 707            settings.edit_predictions.get_or_insert_default().mode = Some(case.mode);
 708        });
 709
 710        if !case.extra_bindings.is_empty() {
 711            cx.update(|cx| cx.bind_keys(case.extra_bindings.clone()));
 712        }
 713
 714        let mut cx = EditorTestContext::new(cx).await;
 715        let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 716        assign_editor_completion_provider(provider.clone(), &mut cx);
 717
 718        match case.state {
 719            InlineKeybindState::Normal | InlineKeybindState::ShowingCompletions => {
 720                cx.set_state("let x = ˇ;");
 721            }
 722            InlineKeybindState::InLeadingWhitespace
 723            | InlineKeybindState::ShowingCompletionsAndLeadingWhitespace => {
 724                cx.set_state(indoc! {"
 725                    fn main() {
 726                        ˇ
 727                    }
 728                "});
 729            }
 730        }
 731
 732        propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 733        cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 734
 735        if matches!(
 736            case.state,
 737            InlineKeybindState::ShowingCompletions
 738                | InlineKeybindState::ShowingCompletionsAndLeadingWhitespace
 739        ) {
 740            assign_editor_completion_menu_provider(&mut cx);
 741            cx.update_editor(|editor, window, cx| {
 742                editor.show_completions(&ShowCompletions, window, cx);
 743            });
 744            cx.run_until_parked();
 745        }
 746
 747        cx.update_editor(|editor, window, cx| {
 748            assert!(
 749                editor.has_active_edit_prediction(),
 750                "case '{}' should have an active edit prediction",
 751                case.name
 752            );
 753
 754            let keybind_display = editor.edit_prediction_keybind_display(
 755                EditPredictionKeybindSurface::Inline,
 756                window,
 757                cx,
 758            );
 759            let accept_keystroke = keybind_display
 760                .accept_keystroke
 761                .as_ref()
 762                .unwrap_or_else(|| panic!("case '{}' should have an accept binding", case.name));
 763            let preview_keystroke = keybind_display
 764                .preview_keystroke
 765                .as_ref()
 766                .unwrap_or_else(|| panic!("case '{}' should have a preview binding", case.name));
 767            let displayed_keystroke = keybind_display
 768                .displayed_keystroke
 769                .as_ref()
 770                .unwrap_or_else(|| panic!("case '{}' should have a displayed binding", case.name));
 771
 772            let expected_accept_keystroke = match case.expected_accept_keystroke {
 773                ExpectedKeystroke::DefaultAccept => default_accept_keystroke.clone(),
 774                ExpectedKeystroke::DefaultPreview => default_preview_keystroke.clone(),
 775                ExpectedKeystroke::Literal(keystroke) => KeybindingKeystroke::from_keystroke(
 776                    Keystroke::parse(keystroke).expect("expected test keystroke to parse"),
 777                ),
 778            };
 779            let expected_preview_keystroke = match case.expected_preview_keystroke {
 780                ExpectedKeystroke::DefaultAccept => default_accept_keystroke.clone(),
 781                ExpectedKeystroke::DefaultPreview => default_preview_keystroke.clone(),
 782                ExpectedKeystroke::Literal(keystroke) => KeybindingKeystroke::from_keystroke(
 783                    Keystroke::parse(keystroke).expect("expected test keystroke to parse"),
 784                ),
 785            };
 786            let expected_displayed_keystroke = match case.expected_displayed_keystroke {
 787                ExpectedKeystroke::DefaultAccept => default_accept_keystroke.clone(),
 788                ExpectedKeystroke::DefaultPreview => default_preview_keystroke.clone(),
 789                ExpectedKeystroke::Literal(keystroke) => KeybindingKeystroke::from_keystroke(
 790                    Keystroke::parse(keystroke).expect("expected test keystroke to parse"),
 791                ),
 792            };
 793
 794            assert_eq!(
 795                accept_keystroke, &expected_accept_keystroke,
 796                "case '{}' selected the wrong accept binding",
 797                case.name
 798            );
 799            assert_eq!(
 800                preview_keystroke, &expected_preview_keystroke,
 801                "case '{}' selected the wrong preview binding",
 802                case.name
 803            );
 804            assert_eq!(
 805                displayed_keystroke, &expected_displayed_keystroke,
 806                "case '{}' selected the wrong displayed binding",
 807                case.name
 808            );
 809
 810            if matches!(case.mode, EditPredictionsMode::Subtle) {
 811                assert!(
 812                    editor.edit_prediction_requires_modifier(),
 813                    "case '{}' should require a modifier",
 814                    case.name
 815                );
 816            }
 817        });
 818    }
 819}
 820
 821#[gpui::test]
 822async fn test_tab_accepts_edit_prediction_over_completion(cx: &mut gpui::TestAppContext) {
 823    init_test(cx, |_| {});
 824    load_default_keymap(cx);
 825
 826    let mut cx = EditorTestContext::new(cx).await;
 827    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 828    assign_editor_completion_provider(provider.clone(), &mut cx);
 829    cx.set_state("let x = ˇ;");
 830
 831    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 832    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 833
 834    assert_editor_active_edit_completion(&mut cx, |_, edits| {
 835        assert_eq!(edits.len(), 1);
 836        assert_eq!(edits[0].1.as_ref(), "42");
 837    });
 838
 839    cx.simulate_keystroke("tab");
 840    cx.run_until_parked();
 841
 842    cx.assert_editor_state("let x = 42ˇ;");
 843}
 844
 845#[gpui::test]
 846async fn test_cursor_popover_edit_prediction_keybind_cases(cx: &mut gpui::TestAppContext) {
 847    enum CursorPopoverPredictionKind {
 848        SingleLine,
 849        MultiLine,
 850        SingleLineWithPreview,
 851        MultiLineWithPreview,
 852        DeleteSingleNewline,
 853        StaleSingleLineAfterMultiLine,
 854    }
 855
 856    struct CursorPopoverCase {
 857        name: &'static str,
 858        prediction_kind: CursorPopoverPredictionKind,
 859        expected_action: EditPredictionKeybindAction,
 860    }
 861
 862    let cases = [
 863        CursorPopoverCase {
 864            name: "single line prediction uses accept action",
 865            prediction_kind: CursorPopoverPredictionKind::SingleLine,
 866            expected_action: EditPredictionKeybindAction::Accept,
 867        },
 868        CursorPopoverCase {
 869            name: "multi line prediction uses preview action",
 870            prediction_kind: CursorPopoverPredictionKind::MultiLine,
 871            expected_action: EditPredictionKeybindAction::Preview,
 872        },
 873        CursorPopoverCase {
 874            name: "single line prediction with preview still uses accept action",
 875            prediction_kind: CursorPopoverPredictionKind::SingleLineWithPreview,
 876            expected_action: EditPredictionKeybindAction::Accept,
 877        },
 878        CursorPopoverCase {
 879            name: "multi line prediction with preview uses preview action",
 880            prediction_kind: CursorPopoverPredictionKind::MultiLineWithPreview,
 881            expected_action: EditPredictionKeybindAction::Preview,
 882        },
 883        CursorPopoverCase {
 884            name: "single line newline deletion uses accept action",
 885            prediction_kind: CursorPopoverPredictionKind::DeleteSingleNewline,
 886            expected_action: EditPredictionKeybindAction::Accept,
 887        },
 888        CursorPopoverCase {
 889            name: "stale multi line prediction does not force preview action",
 890            prediction_kind: CursorPopoverPredictionKind::StaleSingleLineAfterMultiLine,
 891            expected_action: EditPredictionKeybindAction::Accept,
 892        },
 893    ];
 894
 895    for case in cases {
 896        init_test(cx, |_| {});
 897        load_default_keymap(cx);
 898
 899        let mut cx = EditorTestContext::new(cx).await;
 900        let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 901        assign_editor_completion_provider(provider.clone(), &mut cx);
 902
 903        match case.prediction_kind {
 904            CursorPopoverPredictionKind::SingleLine => {
 905                cx.set_state("let x = ˇ;");
 906                propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 907                cx.update_editor(|editor, window, cx| {
 908                    editor.update_visible_edit_prediction(window, cx)
 909                });
 910            }
 911            CursorPopoverPredictionKind::MultiLine => {
 912                cx.set_state("let x = ˇ;");
 913                propose_edits(&provider, vec![(8..8, "42\n43")], &mut cx);
 914                cx.update_editor(|editor, window, cx| {
 915                    editor.update_visible_edit_prediction(window, cx)
 916                });
 917            }
 918            CursorPopoverPredictionKind::SingleLineWithPreview => {
 919                cx.set_state("let x = ˇ;");
 920                propose_edits_with_preview(&provider, vec![(8..8, "42")], &mut cx).await;
 921                cx.update_editor(|editor, window, cx| {
 922                    editor.update_visible_edit_prediction(window, cx)
 923                });
 924            }
 925            CursorPopoverPredictionKind::MultiLineWithPreview => {
 926                cx.set_state("let x = ˇ;");
 927                propose_edits_with_preview(&provider, vec![(8..8, "42\n43")], &mut cx).await;
 928                cx.update_editor(|editor, window, cx| {
 929                    editor.update_visible_edit_prediction(window, cx)
 930                });
 931            }
 932            CursorPopoverPredictionKind::DeleteSingleNewline => {
 933                cx.set_state(indoc! {"
 934                    fn main() {
 935                        let value = 1;
 936                        ˇprintln!(\"done\");
 937                    }
 938                "});
 939                propose_edits(
 940                    &provider,
 941                    vec![(Point::new(1, 18)..Point::new(2, 17), "")],
 942                    &mut cx,
 943                );
 944                cx.update_editor(|editor, window, cx| {
 945                    editor.update_visible_edit_prediction(window, cx)
 946                });
 947            }
 948            CursorPopoverPredictionKind::StaleSingleLineAfterMultiLine => {
 949                cx.set_state("let x = ˇ;");
 950                propose_edits(&provider, vec![(8..8, "42\n43")], &mut cx);
 951                cx.update_editor(|editor, window, cx| {
 952                    editor.update_visible_edit_prediction(window, cx)
 953                });
 954                cx.update_editor(|editor, _window, cx| {
 955                    assert!(editor.active_edit_prediction.is_some());
 956                    assert!(editor.stale_edit_prediction_in_menu.is_none());
 957                    editor.take_active_edit_prediction(true, cx);
 958                    assert!(editor.active_edit_prediction.is_none());
 959                    assert!(editor.stale_edit_prediction_in_menu.is_some());
 960                });
 961
 962                propose_edits(&provider, vec![(8..8, "42")], &mut cx);
 963                cx.update_editor(|editor, window, cx| {
 964                    editor.update_visible_edit_prediction(window, cx)
 965                });
 966            }
 967        }
 968
 969        cx.update_editor(|editor, window, cx| {
 970            assert!(
 971                editor.has_active_edit_prediction(),
 972                "case '{}' should have an active edit prediction",
 973                case.name
 974            );
 975
 976            let keybind_display = editor.edit_prediction_keybind_display(
 977                EditPredictionKeybindSurface::CursorPopoverExpanded,
 978                window,
 979                cx,
 980            );
 981            let accept_keystroke = keybind_display
 982                .accept_keystroke
 983                .as_ref()
 984                .unwrap_or_else(|| panic!("case '{}' should have an accept binding", case.name));
 985            let preview_keystroke = keybind_display
 986                .preview_keystroke
 987                .as_ref()
 988                .unwrap_or_else(|| panic!("case '{}' should have a preview binding", case.name));
 989
 990            assert_eq!(
 991                keybind_display.action, case.expected_action,
 992                "case '{}' selected the wrong cursor popover action",
 993                case.name
 994            );
 995            assert_eq!(
 996                accept_keystroke.key(),
 997                "tab",
 998                "case '{}' selected the wrong accept binding",
 999                case.name
1000            );
1001            assert!(
1002                preview_keystroke.modifiers().modified(),
1003                "case '{}' should use a modified preview binding",
1004                case.name
1005            );
1006
1007            if matches!(
1008                case.prediction_kind,
1009                CursorPopoverPredictionKind::StaleSingleLineAfterMultiLine
1010            ) {
1011                assert!(
1012                    editor.stale_edit_prediction_in_menu.is_none(),
1013                    "case '{}' should clear stale menu state",
1014                    case.name
1015                );
1016            }
1017        });
1018    }
1019}
1020
1021fn assert_editor_active_edit_completion(
1022    cx: &mut EditorTestContext,
1023    assert: impl FnOnce(MultiBufferSnapshot, &Vec<(Range<Anchor>, Arc<str>)>),
1024) {
1025    cx.editor(|editor, _, cx| {
1026        let completion_state = editor
1027            .active_edit_prediction
1028            .as_ref()
1029            .expect("editor has no active completion");
1030
1031        if let EditPrediction::Edit { edits, .. } = &completion_state.completion {
1032            assert(editor.buffer().read(cx).snapshot(cx), edits);
1033        } else {
1034            panic!("expected edit completion");
1035        }
1036    })
1037}
1038
1039fn assert_editor_active_move_completion(
1040    cx: &mut EditorTestContext,
1041    assert: impl FnOnce(MultiBufferSnapshot, Anchor),
1042) {
1043    cx.editor(|editor, _, cx| {
1044        let completion_state = editor
1045            .active_edit_prediction
1046            .as_ref()
1047            .expect("editor has no active completion");
1048
1049        if let EditPrediction::MoveWithin { target, .. } = &completion_state.completion {
1050            assert(editor.buffer().read(cx).snapshot(cx), *target);
1051        } else {
1052            panic!("expected move completion");
1053        }
1054    })
1055}
1056
1057#[gpui::test]
1058async fn test_cancel_clears_stale_edit_prediction_in_menu(cx: &mut gpui::TestAppContext) {
1059    init_test(cx, |_| {});
1060    load_default_keymap(cx);
1061
1062    let mut cx = EditorTestContext::new(cx).await;
1063    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
1064    assign_editor_completion_provider(provider.clone(), &mut cx);
1065    cx.set_state("let x = ˇ;");
1066
1067    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
1068    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
1069
1070    cx.update_editor(|editor, _window, _cx| {
1071        assert!(editor.active_edit_prediction.is_some());
1072        assert!(editor.stale_edit_prediction_in_menu.is_none());
1073    });
1074
1075    cx.simulate_keystroke("escape");
1076    cx.run_until_parked();
1077
1078    cx.update_editor(|editor, _window, _cx| {
1079        assert!(editor.active_edit_prediction.is_none());
1080        assert!(editor.stale_edit_prediction_in_menu.is_none());
1081    });
1082}
1083
1084#[gpui::test]
1085async fn test_discard_clears_delegate_completion(cx: &mut gpui::TestAppContext) {
1086    init_test(cx, |_| {});
1087    load_default_keymap(cx);
1088
1089    let mut cx = EditorTestContext::new(cx).await;
1090    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
1091    assign_editor_completion_provider(provider.clone(), &mut cx);
1092    cx.set_state("let x = ˇ;");
1093
1094    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
1095    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
1096
1097    cx.update_editor(|editor, _window, _cx| {
1098        assert!(editor.active_edit_prediction.is_some());
1099    });
1100
1101    // Dismiss the prediction — this must call discard() on the delegate,
1102    // which should clear self.completion.
1103    cx.simulate_keystroke("escape");
1104    cx.run_until_parked();
1105
1106    cx.update_editor(|editor, _window, _cx| {
1107        assert!(editor.active_edit_prediction.is_none());
1108    });
1109
1110    // update_visible_edit_prediction must NOT bring the prediction back,
1111    // because discard() cleared self.completion in the delegate.
1112    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
1113
1114    cx.update_editor(|editor, _window, _cx| {
1115        assert!(
1116            editor.active_edit_prediction.is_none(),
1117            "prediction must not resurface after discard()"
1118        );
1119    });
1120}
1121
1122fn accept_completion(cx: &mut EditorTestContext) {
1123    cx.update_editor(|editor, window, cx| {
1124        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
1125    })
1126}
1127
1128fn propose_edits<T: ToOffset>(
1129    provider: &Entity<FakeEditPredictionDelegate>,
1130    edits: Vec<(Range<T>, &str)>,
1131    cx: &mut EditorTestContext,
1132) {
1133    propose_edits_with_cursor_position(provider, edits, None, cx);
1134}
1135
1136async fn propose_edits_with_preview<T: ToOffset + Clone>(
1137    provider: &Entity<FakeEditPredictionDelegate>,
1138    edits: Vec<(Range<T>, &str)>,
1139    cx: &mut EditorTestContext,
1140) {
1141    let snapshot = cx.buffer_snapshot();
1142    let edits = edits
1143        .into_iter()
1144        .map(|(range, text)| {
1145            let anchor_range =
1146                snapshot.anchor_after(range.start.clone())..snapshot.anchor_before(range.end);
1147            (anchor_range, Arc::<str>::from(text))
1148        })
1149        .collect::<Vec<_>>();
1150
1151    let preview_edits = edits
1152        .iter()
1153        .map(|(range, text)| (range.clone(), text.clone()))
1154        .collect::<Arc<[_]>>();
1155
1156    let edit_preview = cx
1157        .buffer(|buffer: &Buffer, app| buffer.preview_edits(preview_edits, app))
1158        .await;
1159
1160    let provider_edits = edits.into_iter().collect();
1161
1162    cx.update(|_, cx| {
1163        provider.update(cx, |provider, _| {
1164            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1165                id: None,
1166                edits: provider_edits,
1167                cursor_position: None,
1168                edit_preview: Some(edit_preview),
1169            }))
1170        })
1171    });
1172}
1173
1174fn propose_edits_with_cursor_position<T: ToOffset>(
1175    provider: &Entity<FakeEditPredictionDelegate>,
1176    edits: Vec<(Range<T>, &str)>,
1177    cursor_offset: Option<usize>,
1178    cx: &mut EditorTestContext,
1179) {
1180    let snapshot = cx.buffer_snapshot();
1181    let cursor_position = cursor_offset
1182        .map(|offset| PredictedCursorPosition::at_anchor(snapshot.anchor_after(offset)));
1183    let edits = edits.into_iter().map(|(range, text)| {
1184        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1185        (range, text.into())
1186    });
1187
1188    cx.update(|_, cx| {
1189        provider.update(cx, |provider, _| {
1190            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1191                id: None,
1192                edits: edits.collect(),
1193                cursor_position,
1194                edit_preview: None,
1195            }))
1196        })
1197    });
1198}
1199
1200fn propose_edits_with_cursor_position_in_insertion<T: ToOffset>(
1201    provider: &Entity<FakeEditPredictionDelegate>,
1202    edits: Vec<(Range<T>, &str)>,
1203    anchor_offset: usize,
1204    offset_within_insertion: usize,
1205    cx: &mut EditorTestContext,
1206) {
1207    let snapshot = cx.buffer_snapshot();
1208    // Use anchor_before (left bias) so the anchor stays at the insertion point
1209    // rather than moving past the inserted text
1210    let cursor_position = Some(PredictedCursorPosition::new(
1211        snapshot.anchor_before(anchor_offset),
1212        offset_within_insertion,
1213    ));
1214    let edits = edits.into_iter().map(|(range, text)| {
1215        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1216        (range, text.into())
1217    });
1218
1219    cx.update(|_, cx| {
1220        provider.update(cx, |provider, _| {
1221            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1222                id: None,
1223                edits: edits.collect(),
1224                cursor_position,
1225                edit_preview: None,
1226            }))
1227        })
1228    });
1229}
1230
1231fn assign_editor_completion_provider(
1232    provider: Entity<FakeEditPredictionDelegate>,
1233    cx: &mut EditorTestContext,
1234) {
1235    cx.update_editor(|editor, window, cx| {
1236        editor.set_edit_prediction_provider(Some(provider), window, cx);
1237    })
1238}
1239
1240fn assign_editor_completion_menu_provider(cx: &mut EditorTestContext) {
1241    cx.update_editor(|editor, _, _| {
1242        editor.set_completion_provider(Some(Rc::new(FakeCompletionMenuProvider)));
1243    });
1244}
1245
1246fn propose_edits_non_zed<T: ToOffset>(
1247    provider: &Entity<FakeNonZedEditPredictionDelegate>,
1248    edits: Vec<(Range<T>, &str)>,
1249    cx: &mut EditorTestContext,
1250) {
1251    let snapshot = cx.buffer_snapshot();
1252    let edits = edits.into_iter().map(|(range, text)| {
1253        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1254        (range, text.into())
1255    });
1256
1257    cx.update(|_, cx| {
1258        provider.update(cx, |provider, _| {
1259            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1260                id: None,
1261                edits: edits.collect(),
1262                cursor_position: None,
1263                edit_preview: None,
1264            }))
1265        })
1266    });
1267}
1268
1269fn assign_editor_completion_provider_non_zed(
1270    provider: Entity<FakeNonZedEditPredictionDelegate>,
1271    cx: &mut EditorTestContext,
1272) {
1273    cx.update_editor(|editor, window, cx| {
1274        editor.set_edit_prediction_provider(Some(provider), window, cx);
1275    })
1276}
1277
1278struct FakeCompletionMenuProvider;
1279
1280impl CompletionProvider for FakeCompletionMenuProvider {
1281    fn completions(
1282        &self,
1283        buffer: &Entity<Buffer>,
1284        _buffer_position: text::Anchor,
1285        _trigger: CompletionContext,
1286        _window: &mut Window,
1287        cx: &mut Context<crate::Editor>,
1288    ) -> Task<anyhow::Result<Vec<CompletionResponse>>> {
1289        let completion = Completion {
1290            replace_range: text::Anchor::min_max_range_for_buffer(buffer.read(cx).remote_id()),
1291            new_text: "fake_completion".to_string(),
1292            label: CodeLabel::plain("fake_completion".to_string(), None),
1293            documentation: None,
1294            source: CompletionSource::Custom,
1295            icon_path: None,
1296            match_start: None,
1297            snippet_deduplication_key: None,
1298            insert_text_mode: None,
1299            confirm: None,
1300        };
1301
1302        Task::ready(Ok(vec![CompletionResponse {
1303            completions: vec![completion],
1304            display_options: Default::default(),
1305            is_incomplete: false,
1306        }]))
1307    }
1308
1309    fn is_completion_trigger(
1310        &self,
1311        _buffer: &Entity<Buffer>,
1312        _position: language::Anchor,
1313        _text: &str,
1314        _trigger_in_words: bool,
1315        _cx: &mut Context<crate::Editor>,
1316    ) -> bool {
1317        false
1318    }
1319
1320    fn filter_completions(&self) -> bool {
1321        false
1322    }
1323}
1324
1325#[derive(Default, Clone)]
1326pub struct FakeEditPredictionDelegate {
1327    pub completion: Option<edit_prediction_types::EditPrediction>,
1328    pub refresh_count: Arc<AtomicUsize>,
1329}
1330
1331impl FakeEditPredictionDelegate {
1332    pub fn set_edit_prediction(
1333        &mut self,
1334        completion: Option<edit_prediction_types::EditPrediction>,
1335    ) {
1336        self.completion = completion;
1337    }
1338}
1339
1340impl EditPredictionDelegate for FakeEditPredictionDelegate {
1341    fn name() -> &'static str {
1342        "fake-completion-provider"
1343    }
1344
1345    fn display_name() -> &'static str {
1346        "Fake Completion Provider"
1347    }
1348
1349    fn show_predictions_in_menu() -> bool {
1350        true
1351    }
1352
1353    fn supports_jump_to_edit() -> bool {
1354        true
1355    }
1356
1357    fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
1358        EditPredictionIconSet::new(IconName::ZedPredict)
1359    }
1360
1361    fn is_enabled(
1362        &self,
1363        _buffer: &gpui::Entity<language::Buffer>,
1364        _cursor_position: language::Anchor,
1365        _cx: &gpui::App,
1366    ) -> bool {
1367        true
1368    }
1369
1370    fn is_refreshing(&self, _cx: &gpui::App) -> bool {
1371        false
1372    }
1373
1374    fn refresh(
1375        &mut self,
1376        _buffer: gpui::Entity<language::Buffer>,
1377        _cursor_position: language::Anchor,
1378        _debounce: bool,
1379        _cx: &mut gpui::Context<Self>,
1380    ) {
1381        self.refresh_count.fetch_add(1, atomic::Ordering::SeqCst);
1382    }
1383
1384    fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
1385
1386    fn discard(
1387        &mut self,
1388        _reason: edit_prediction_types::EditPredictionDiscardReason,
1389        _cx: &mut gpui::Context<Self>,
1390    ) {
1391        self.completion.take();
1392    }
1393
1394    fn suggest<'a>(
1395        &mut self,
1396        _buffer: &gpui::Entity<language::Buffer>,
1397        _cursor_position: language::Anchor,
1398        _cx: &mut gpui::Context<Self>,
1399    ) -> Option<edit_prediction_types::EditPrediction> {
1400        self.completion.clone()
1401    }
1402}
1403
1404#[derive(Default, Clone)]
1405pub struct FakeNonZedEditPredictionDelegate {
1406    pub completion: Option<edit_prediction_types::EditPrediction>,
1407}
1408
1409impl FakeNonZedEditPredictionDelegate {
1410    pub fn set_edit_prediction(
1411        &mut self,
1412        completion: Option<edit_prediction_types::EditPrediction>,
1413    ) {
1414        self.completion = completion;
1415    }
1416}
1417
1418impl EditPredictionDelegate for FakeNonZedEditPredictionDelegate {
1419    fn name() -> &'static str {
1420        "fake-non-zed-provider"
1421    }
1422
1423    fn display_name() -> &'static str {
1424        "Fake Non-Zed Provider"
1425    }
1426
1427    fn show_predictions_in_menu() -> bool {
1428        false
1429    }
1430
1431    fn supports_jump_to_edit() -> bool {
1432        false
1433    }
1434
1435    fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
1436        EditPredictionIconSet::new(IconName::ZedPredict)
1437    }
1438
1439    fn is_enabled(
1440        &self,
1441        _buffer: &gpui::Entity<language::Buffer>,
1442        _cursor_position: language::Anchor,
1443        _cx: &gpui::App,
1444    ) -> bool {
1445        true
1446    }
1447
1448    fn is_refreshing(&self, _cx: &gpui::App) -> bool {
1449        false
1450    }
1451
1452    fn refresh(
1453        &mut self,
1454        _buffer: gpui::Entity<language::Buffer>,
1455        _cursor_position: language::Anchor,
1456        _debounce: bool,
1457        _cx: &mut gpui::Context<Self>,
1458    ) {
1459    }
1460
1461    fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
1462
1463    fn discard(
1464        &mut self,
1465        _reason: edit_prediction_types::EditPredictionDiscardReason,
1466        _cx: &mut gpui::Context<Self>,
1467    ) {
1468        self.completion.take();
1469    }
1470
1471    fn suggest<'a>(
1472        &mut self,
1473        _buffer: &gpui::Entity<language::Buffer>,
1474        _cursor_position: language::Anchor,
1475        _cx: &mut gpui::Context<Self>,
1476    ) -> Option<edit_prediction_types::EditPrediction> {
1477        self.completion.clone()
1478    }
1479}