edit_prediction_tests.rs

  1use edit_prediction_types::{
  2    EditPredictionDelegate, EditPredictionIconSet, PredictedCursorPosition,
  3};
  4use gpui::{Entity, KeyBinding, Modifiers, prelude::*};
  5use indoc::indoc;
  6use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
  7use std::{
  8    ops::Range,
  9    sync::{
 10        Arc,
 11        atomic::{self, AtomicUsize},
 12    },
 13};
 14use text::{Point, ToOffset};
 15use ui::prelude::*;
 16
 17use crate::{
 18    AcceptEditPrediction, EditPrediction, MenuEditPredictionsPolicy, editor_tests::init_test,
 19    test::editor_test_context::EditorTestContext,
 20};
 21use rpc::proto::PeerId;
 22use workspace::CollaboratorId;
 23
 24#[gpui::test]
 25async fn test_edit_prediction_insert(cx: &mut gpui::TestAppContext) {
 26    init_test(cx, |_| {});
 27
 28    let mut cx = EditorTestContext::new(cx).await;
 29    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 30    assign_editor_completion_provider(provider.clone(), &mut cx);
 31    cx.set_state("let absolute_zero_celsius = ˇ;");
 32
 33    propose_edits(&provider, vec![(28..28, "-273.15")], &mut cx);
 34    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 35
 36    assert_editor_active_edit_completion(&mut cx, |_, edits| {
 37        assert_eq!(edits.len(), 1);
 38        assert_eq!(edits[0].1.as_ref(), "-273.15");
 39    });
 40
 41    accept_completion(&mut cx);
 42
 43    cx.assert_editor_state("let absolute_zero_celsius = -273.15ˇ;")
 44}
 45
 46#[gpui::test]
 47async fn test_edit_prediction_cursor_position_inside_insertion(cx: &mut gpui::TestAppContext) {
 48    init_test(cx, |_| {
 49        eprintln!("");
 50    });
 51
 52    let mut cx = EditorTestContext::new(cx).await;
 53    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 54
 55    assign_editor_completion_provider(provider.clone(), &mut cx);
 56    // Buffer: "fn foo() {}" - we'll insert text and position cursor inside the insertion
 57    cx.set_state("fn foo() ˇ{}");
 58
 59    // Insert "bar()" at offset 9, with cursor at offset 2 within the insertion (after "ba")
 60    // This tests the case where cursor is inside newly inserted text
 61    propose_edits_with_cursor_position_in_insertion(
 62        &provider,
 63        vec![(9..9, "bar()")],
 64        9, // anchor at the insertion point
 65        2, // offset 2 within "bar()" puts cursor after "ba"
 66        &mut cx,
 67    );
 68    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 69
 70    assert_editor_active_edit_completion(&mut cx, |_, edits| {
 71        assert_eq!(edits.len(), 1);
 72        assert_eq!(edits[0].1.as_ref(), "bar()");
 73    });
 74
 75    accept_completion(&mut cx);
 76
 77    // Cursor should be inside the inserted text at "baˇr()"
 78    cx.assert_editor_state("fn foo() baˇr(){}");
 79}
 80
 81#[gpui::test]
 82async fn test_edit_prediction_cursor_position_outside_edit(cx: &mut gpui::TestAppContext) {
 83    init_test(cx, |_| {});
 84
 85    let mut cx = EditorTestContext::new(cx).await;
 86    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 87    assign_editor_completion_provider(provider.clone(), &mut cx);
 88    // Buffer: "let x = ;" with cursor before semicolon - we'll insert "42" and position cursor elsewhere
 89    cx.set_state("let x = ˇ;");
 90
 91    // Insert "42" at offset 8, but set cursor_position to offset 4 (the 'x')
 92    // This tests that cursor moves to the predicted position, not the end of the edit
 93    propose_edits_with_cursor_position(
 94        &provider,
 95        vec![(8..8, "42")],
 96        Some(4), // cursor at offset 4 (the 'x'), NOT at the edit location
 97        &mut cx,
 98    );
 99    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
100
101    assert_editor_active_edit_completion(&mut cx, |_, edits| {
102        assert_eq!(edits.len(), 1);
103        assert_eq!(edits[0].1.as_ref(), "42");
104    });
105
106    accept_completion(&mut cx);
107
108    // Cursor should be at offset 4 (the 'x'), not at the end of the inserted "42"
109    cx.assert_editor_state("let ˇx = 42;");
110}
111
112#[gpui::test]
113async fn test_edit_prediction_cursor_position_fallback(cx: &mut gpui::TestAppContext) {
114    init_test(cx, |_| {});
115
116    let mut cx = EditorTestContext::new(cx).await;
117    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
118    assign_editor_completion_provider(provider.clone(), &mut cx);
119    cx.set_state("let x = ˇ;");
120
121    // Propose an edit without a cursor position - should fall back to end of edit
122    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
123    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
124
125    accept_completion(&mut cx);
126
127    // Cursor should be at the end of the inserted text (default behavior)
128    cx.assert_editor_state("let x = 42ˇ;")
129}
130
131#[gpui::test]
132async fn test_edit_prediction_modification(cx: &mut gpui::TestAppContext) {
133    init_test(cx, |_| {});
134
135    let mut cx = EditorTestContext::new(cx).await;
136    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
137    assign_editor_completion_provider(provider.clone(), &mut cx);
138    cx.set_state("let pi = ˇ\"foo\";");
139
140    propose_edits(&provider, vec![(9..14, "3.14159")], &mut cx);
141    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
142
143    assert_editor_active_edit_completion(&mut cx, |_, edits| {
144        assert_eq!(edits.len(), 1);
145        assert_eq!(edits[0].1.as_ref(), "3.14159");
146    });
147
148    accept_completion(&mut cx);
149
150    cx.assert_editor_state("let pi = 3.14159ˇ;")
151}
152
153#[gpui::test]
154async fn test_edit_prediction_jump_button(cx: &mut gpui::TestAppContext) {
155    init_test(cx, |_| {});
156
157    let mut cx = EditorTestContext::new(cx).await;
158    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
159    assign_editor_completion_provider(provider.clone(), &mut cx);
160
161    // Cursor is 2+ lines above the proposed edit
162    cx.set_state(indoc! {"
163        line 0
164        line ˇ1
165        line 2
166        line 3
167        line
168    "});
169
170    propose_edits(
171        &provider,
172        vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],
173        &mut cx,
174    );
175
176    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
177    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
178        assert_eq!(move_target.to_point(&snapshot), Point::new(4, 3));
179    });
180
181    // When accepting, cursor is moved to the proposed location
182    accept_completion(&mut cx);
183    cx.assert_editor_state(indoc! {"
184        line 0
185        line 1
186        line 2
187        line 3
188        linˇe
189    "});
190
191    // Cursor is 2+ lines below the proposed edit
192    cx.set_state(indoc! {"
193        line 0
194        line
195        line 2
196        line 3
197        line ˇ4
198    "});
199
200    propose_edits(
201        &provider,
202        vec![(Point::new(1, 3)..Point::new(1, 3), " 1")],
203        &mut cx,
204    );
205
206    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
207    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
208        assert_eq!(move_target.to_point(&snapshot), Point::new(1, 3));
209    });
210
211    // When accepting, cursor is moved to the proposed location
212    accept_completion(&mut cx);
213    cx.assert_editor_state(indoc! {"
214        line 0
215        linˇe
216        line 2
217        line 3
218        line 4
219    "});
220}
221
222#[gpui::test]
223async fn test_edit_prediction_invalidation_range(cx: &mut gpui::TestAppContext) {
224    init_test(cx, |_| {});
225
226    let mut cx = EditorTestContext::new(cx).await;
227    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
228    assign_editor_completion_provider(provider.clone(), &mut cx);
229
230    // Cursor is 3+ lines above the proposed edit
231    cx.set_state(indoc! {"
232        line 0
233        line ˇ1
234        line 2
235        line 3
236        line 4
237        line
238    "});
239    let edit_location = Point::new(5, 3);
240
241    propose_edits(
242        &provider,
243        vec![(edit_location..edit_location, " 5")],
244        &mut cx,
245    );
246
247    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
248    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
249        assert_eq!(move_target.to_point(&snapshot), edit_location);
250    });
251
252    // If we move *towards* the completion, it stays active
253    cx.set_selections_state(indoc! {"
254        line 0
255        line 1
256        line ˇ2
257        line 3
258        line 4
259        line
260    "});
261    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
262        assert_eq!(move_target.to_point(&snapshot), edit_location);
263    });
264
265    // If we move *away* from the completion, it is discarded
266    cx.set_selections_state(indoc! {"
267        line ˇ0
268        line 1
269        line 2
270        line 3
271        line 4
272        line
273    "});
274    cx.editor(|editor, _, _| {
275        assert!(editor.active_edit_prediction.is_none());
276    });
277
278    // Cursor is 3+ lines below the proposed edit
279    cx.set_state(indoc! {"
280        line
281        line 1
282        line 2
283        line 3
284        line ˇ4
285        line 5
286    "});
287    let edit_location = Point::new(0, 3);
288
289    propose_edits(
290        &provider,
291        vec![(edit_location..edit_location, " 0")],
292        &mut cx,
293    );
294
295    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
296    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
297        assert_eq!(move_target.to_point(&snapshot), edit_location);
298    });
299
300    // If we move *towards* the completion, it stays active
301    cx.set_selections_state(indoc! {"
302        line
303        line 1
304        line 2
305        line ˇ3
306        line 4
307        line 5
308    "});
309    assert_editor_active_move_completion(&mut cx, |snapshot, move_target| {
310        assert_eq!(move_target.to_point(&snapshot), edit_location);
311    });
312
313    // If we move *away* from the completion, it is discarded
314    cx.set_selections_state(indoc! {"
315        line
316        line 1
317        line 2
318        line 3
319        line 4
320        line ˇ5
321    "});
322    cx.editor(|editor, _, _| {
323        assert!(editor.active_edit_prediction.is_none());
324    });
325}
326
327#[gpui::test]
328async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui::TestAppContext) {
329    init_test(cx, |_| {});
330
331    let mut cx = EditorTestContext::new(cx).await;
332    let provider = cx.new(|_| FakeNonZedEditPredictionDelegate::default());
333    assign_editor_completion_provider_non_zed(provider.clone(), &mut cx);
334
335    // Cursor is 2+ lines above the proposed edit
336    cx.set_state(indoc! {"
337        line 0
338        line ˇ1
339        line 2
340        line 3
341        line
342    "});
343
344    propose_edits_non_zed(
345        &provider,
346        vec![(Point::new(4, 3)..Point::new(4, 3), " 4")],
347        &mut cx,
348    );
349
350    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
351
352    // For non-Zed providers, there should be no move completion (jump functionality disabled)
353    cx.editor(|editor, _, _| {
354        if let Some(completion_state) = &editor.active_edit_prediction {
355            // Should be an Edit prediction, not a Move prediction
356            match &completion_state.completion {
357                EditPrediction::Edit { .. } => {
358                    // This is expected for non-Zed providers
359                }
360                EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => {
361                    panic!(
362                        "Non-Zed providers should not show Move predictions (jump functionality)"
363                    );
364                }
365            }
366        }
367    });
368}
369
370#[gpui::test]
371async fn test_edit_prediction_refresh_suppressed_while_following(cx: &mut gpui::TestAppContext) {
372    init_test(cx, |_| {});
373
374    let mut cx = EditorTestContext::new(cx).await;
375    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
376    assign_editor_completion_provider(provider.clone(), &mut cx);
377    cx.set_state("let x = ˇ;");
378
379    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
380
381    cx.update_editor(|editor, window, cx| {
382        editor.refresh_edit_prediction(false, false, window, cx);
383        editor.update_visible_edit_prediction(window, cx);
384    });
385
386    assert_eq!(
387        provider.read_with(&cx.cx, |provider, _| {
388            provider.refresh_count.load(atomic::Ordering::SeqCst)
389        }),
390        1
391    );
392    cx.editor(|editor, _, _| {
393        assert!(editor.active_edit_prediction.is_some());
394    });
395
396    cx.update_editor(|editor, window, cx| {
397        editor.leader_id = Some(CollaboratorId::PeerId(PeerId::default()));
398        editor.refresh_edit_prediction(false, false, window, cx);
399    });
400
401    assert_eq!(
402        provider.read_with(&cx.cx, |provider, _| {
403            provider.refresh_count.load(atomic::Ordering::SeqCst)
404        }),
405        1
406    );
407    cx.editor(|editor, _, _| {
408        assert!(editor.active_edit_prediction.is_none());
409    });
410
411    cx.update_editor(|editor, window, cx| {
412        editor.leader_id = None;
413        editor.refresh_edit_prediction(false, false, window, cx);
414    });
415
416    assert_eq!(
417        provider.read_with(&cx.cx, |provider, _| {
418            provider.refresh_count.load(atomic::Ordering::SeqCst)
419        }),
420        2
421    );
422}
423
424#[gpui::test]
425async fn test_edit_prediction_preview_cleanup_on_toggle_off(cx: &mut gpui::TestAppContext) {
426    init_test(cx, |_| {});
427
428    // Bind `ctrl-shift-a` to accept the provided edit prediction. The actual key
429    // binding here doesn't matter, we simply need to confirm that holding the
430    // binding's modifiers triggers the edit prediction preview.
431    cx.update(|cx| cx.bind_keys([KeyBinding::new("ctrl-shift-a", AcceptEditPrediction, None)]));
432
433    let mut cx = EditorTestContext::new(cx).await;
434    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
435    assign_editor_completion_provider(provider.clone(), &mut cx);
436    cx.set_state("let x = ˇ;");
437
438    propose_edits(&provider, vec![(8..8, "42")], &mut cx);
439    cx.update_editor(|editor, window, cx| {
440        editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::ByProvider);
441        editor.update_visible_edit_prediction(window, cx)
442    });
443
444    cx.editor(|editor, _, _| {
445        assert!(editor.has_active_edit_prediction());
446    });
447
448    // Simulate pressing the modifiers for `AcceptEditPrediction`, namely
449    // `ctrl-shift`, so that we can confirm that the edit prediction preview is
450    // activated.
451    let modifiers = Modifiers::control_shift();
452    cx.simulate_modifiers_change(modifiers);
453    cx.run_until_parked();
454
455    cx.editor(|editor, _, _| {
456        assert!(editor.edit_prediction_preview_is_active());
457    });
458
459    // Disable showing edit predictions without issuing a new modifiers changed
460    // event, to confirm that the edit prediction preview is still active.
461    cx.update_editor(|editor, window, cx| {
462        editor.set_show_edit_predictions(Some(false), window, cx);
463    });
464
465    cx.editor(|editor, _, _| {
466        assert!(!editor.has_active_edit_prediction());
467        assert!(editor.edit_prediction_preview_is_active());
468    });
469
470    // Now release the modifiers
471    // Simulate releasing all modifiers, ensuring that even with edit prediction
472    // disabled, the edit prediction preview is cleaned up.
473    cx.simulate_modifiers_change(Modifiers::none());
474    cx.run_until_parked();
475
476    cx.editor(|editor, _, _| {
477        assert!(!editor.edit_prediction_preview_is_active());
478    });
479}
480
481fn assert_editor_active_edit_completion(
482    cx: &mut EditorTestContext,
483    assert: impl FnOnce(MultiBufferSnapshot, &Vec<(Range<Anchor>, Arc<str>)>),
484) {
485    cx.editor(|editor, _, cx| {
486        let completion_state = editor
487            .active_edit_prediction
488            .as_ref()
489            .expect("editor has no active completion");
490
491        if let EditPrediction::Edit { edits, .. } = &completion_state.completion {
492            assert(editor.buffer().read(cx).snapshot(cx), edits);
493        } else {
494            panic!("expected edit completion");
495        }
496    })
497}
498
499fn assert_editor_active_move_completion(
500    cx: &mut EditorTestContext,
501    assert: impl FnOnce(MultiBufferSnapshot, Anchor),
502) {
503    cx.editor(|editor, _, cx| {
504        let completion_state = editor
505            .active_edit_prediction
506            .as_ref()
507            .expect("editor has no active completion");
508
509        if let EditPrediction::MoveWithin { target, .. } = &completion_state.completion {
510            assert(editor.buffer().read(cx).snapshot(cx), *target);
511        } else {
512            panic!("expected move completion");
513        }
514    })
515}
516
517fn accept_completion(cx: &mut EditorTestContext) {
518    cx.update_editor(|editor, window, cx| {
519        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
520    })
521}
522
523fn propose_edits<T: ToOffset>(
524    provider: &Entity<FakeEditPredictionDelegate>,
525    edits: Vec<(Range<T>, &str)>,
526    cx: &mut EditorTestContext,
527) {
528    propose_edits_with_cursor_position(provider, edits, None, cx);
529}
530
531fn propose_edits_with_cursor_position<T: ToOffset>(
532    provider: &Entity<FakeEditPredictionDelegate>,
533    edits: Vec<(Range<T>, &str)>,
534    cursor_offset: Option<usize>,
535    cx: &mut EditorTestContext,
536) {
537    let snapshot = cx.buffer_snapshot();
538    let cursor_position = cursor_offset
539        .map(|offset| PredictedCursorPosition::at_anchor(snapshot.anchor_after(offset)));
540    let edits = edits.into_iter().map(|(range, text)| {
541        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
542        (range, text.into())
543    });
544
545    cx.update(|_, cx| {
546        provider.update(cx, |provider, _| {
547            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
548                id: None,
549                edits: edits.collect(),
550                cursor_position,
551                edit_preview: None,
552            }))
553        })
554    });
555}
556
557fn propose_edits_with_cursor_position_in_insertion<T: ToOffset>(
558    provider: &Entity<FakeEditPredictionDelegate>,
559    edits: Vec<(Range<T>, &str)>,
560    anchor_offset: usize,
561    offset_within_insertion: usize,
562    cx: &mut EditorTestContext,
563) {
564    let snapshot = cx.buffer_snapshot();
565    // Use anchor_before (left bias) so the anchor stays at the insertion point
566    // rather than moving past the inserted text
567    let cursor_position = Some(PredictedCursorPosition::new(
568        snapshot.anchor_before(anchor_offset),
569        offset_within_insertion,
570    ));
571    let edits = edits.into_iter().map(|(range, text)| {
572        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
573        (range, text.into())
574    });
575
576    cx.update(|_, cx| {
577        provider.update(cx, |provider, _| {
578            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
579                id: None,
580                edits: edits.collect(),
581                cursor_position,
582                edit_preview: None,
583            }))
584        })
585    });
586}
587
588fn assign_editor_completion_provider(
589    provider: Entity<FakeEditPredictionDelegate>,
590    cx: &mut EditorTestContext,
591) {
592    cx.update_editor(|editor, window, cx| {
593        editor.set_edit_prediction_provider(Some(provider), window, cx);
594    })
595}
596
597fn propose_edits_non_zed<T: ToOffset>(
598    provider: &Entity<FakeNonZedEditPredictionDelegate>,
599    edits: Vec<(Range<T>, &str)>,
600    cx: &mut EditorTestContext,
601) {
602    let snapshot = cx.buffer_snapshot();
603    let edits = edits.into_iter().map(|(range, text)| {
604        let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
605        (range, text.into())
606    });
607
608    cx.update(|_, cx| {
609        provider.update(cx, |provider, _| {
610            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
611                id: None,
612                edits: edits.collect(),
613                cursor_position: None,
614                edit_preview: None,
615            }))
616        })
617    });
618}
619
620fn assign_editor_completion_provider_non_zed(
621    provider: Entity<FakeNonZedEditPredictionDelegate>,
622    cx: &mut EditorTestContext,
623) {
624    cx.update_editor(|editor, window, cx| {
625        editor.set_edit_prediction_provider(Some(provider), window, cx);
626    })
627}
628
629#[derive(Default, Clone)]
630pub struct FakeEditPredictionDelegate {
631    pub completion: Option<edit_prediction_types::EditPrediction>,
632    pub refresh_count: Arc<AtomicUsize>,
633}
634
635impl FakeEditPredictionDelegate {
636    pub fn set_edit_prediction(
637        &mut self,
638        completion: Option<edit_prediction_types::EditPrediction>,
639    ) {
640        self.completion = completion;
641    }
642}
643
644impl EditPredictionDelegate for FakeEditPredictionDelegate {
645    fn name() -> &'static str {
646        "fake-completion-provider"
647    }
648
649    fn display_name() -> &'static str {
650        "Fake Completion Provider"
651    }
652
653    fn show_predictions_in_menu() -> bool {
654        true
655    }
656
657    fn supports_jump_to_edit() -> bool {
658        true
659    }
660
661    fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
662        EditPredictionIconSet::new(IconName::ZedPredict)
663    }
664
665    fn is_enabled(
666        &self,
667        _buffer: &gpui::Entity<language::Buffer>,
668        _cursor_position: language::Anchor,
669        _cx: &gpui::App,
670    ) -> bool {
671        true
672    }
673
674    fn is_refreshing(&self, _cx: &gpui::App) -> bool {
675        false
676    }
677
678    fn refresh(
679        &mut self,
680        _buffer: gpui::Entity<language::Buffer>,
681        _cursor_position: language::Anchor,
682        _debounce: bool,
683        _cx: &mut gpui::Context<Self>,
684    ) {
685        self.refresh_count.fetch_add(1, atomic::Ordering::SeqCst);
686    }
687
688    fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
689
690    fn discard(
691        &mut self,
692        _reason: edit_prediction_types::EditPredictionDiscardReason,
693        _cx: &mut gpui::Context<Self>,
694    ) {
695    }
696
697    fn suggest<'a>(
698        &mut self,
699        _buffer: &gpui::Entity<language::Buffer>,
700        _cursor_position: language::Anchor,
701        _cx: &mut gpui::Context<Self>,
702    ) -> Option<edit_prediction_types::EditPrediction> {
703        self.completion.clone()
704    }
705}
706
707#[derive(Default, Clone)]
708pub struct FakeNonZedEditPredictionDelegate {
709    pub completion: Option<edit_prediction_types::EditPrediction>,
710}
711
712impl FakeNonZedEditPredictionDelegate {
713    pub fn set_edit_prediction(
714        &mut self,
715        completion: Option<edit_prediction_types::EditPrediction>,
716    ) {
717        self.completion = completion;
718    }
719}
720
721impl EditPredictionDelegate for FakeNonZedEditPredictionDelegate {
722    fn name() -> &'static str {
723        "fake-non-zed-provider"
724    }
725
726    fn display_name() -> &'static str {
727        "Fake Non-Zed Provider"
728    }
729
730    fn show_predictions_in_menu() -> bool {
731        false
732    }
733
734    fn supports_jump_to_edit() -> bool {
735        false
736    }
737
738    fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
739        EditPredictionIconSet::new(IconName::ZedPredict)
740    }
741
742    fn is_enabled(
743        &self,
744        _buffer: &gpui::Entity<language::Buffer>,
745        _cursor_position: language::Anchor,
746        _cx: &gpui::App,
747    ) -> bool {
748        true
749    }
750
751    fn is_refreshing(&self, _cx: &gpui::App) -> bool {
752        false
753    }
754
755    fn refresh(
756        &mut self,
757        _buffer: gpui::Entity<language::Buffer>,
758        _cursor_position: language::Anchor,
759        _debounce: bool,
760        _cx: &mut gpui::Context<Self>,
761    ) {
762    }
763
764    fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
765
766    fn discard(
767        &mut self,
768        _reason: edit_prediction_types::EditPredictionDiscardReason,
769        _cx: &mut gpui::Context<Self>,
770    ) {
771    }
772
773    fn suggest<'a>(
774        &mut self,
775        _buffer: &gpui::Entity<language::Buffer>,
776        _cursor_position: language::Anchor,
777        _cx: &mut gpui::Context<Self>,
778    ) -> Option<edit_prediction_types::EditPrediction> {
779        self.completion.clone()
780    }
781}