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, ExcerptId, 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(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
1057fn accept_completion(cx: &mut EditorTestContext) {
1058 cx.update_editor(|editor, window, cx| {
1059 editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
1060 })
1061}
1062
1063fn propose_edits<T: ToOffset>(
1064 provider: &Entity<FakeEditPredictionDelegate>,
1065 edits: Vec<(Range<T>, &str)>,
1066 cx: &mut EditorTestContext,
1067) {
1068 propose_edits_with_cursor_position(provider, edits, None, cx);
1069}
1070
1071async fn propose_edits_with_preview<T: ToOffset + Clone>(
1072 provider: &Entity<FakeEditPredictionDelegate>,
1073 edits: Vec<(Range<T>, &str)>,
1074 cx: &mut EditorTestContext,
1075) {
1076 let snapshot = cx.buffer_snapshot();
1077 let edits = edits
1078 .into_iter()
1079 .map(|(range, text)| {
1080 let anchor_range =
1081 snapshot.anchor_after(range.start.clone())..snapshot.anchor_before(range.end);
1082 (anchor_range, Arc::<str>::from(text))
1083 })
1084 .collect::<Vec<_>>();
1085
1086 let preview_edits = edits
1087 .iter()
1088 .map(|(range, text)| (range.clone(), text.clone()))
1089 .collect::<Arc<[_]>>();
1090
1091 let edit_preview = cx
1092 .buffer(|buffer: &Buffer, app| buffer.preview_edits(preview_edits, app))
1093 .await;
1094
1095 let provider_edits = edits.into_iter().collect();
1096
1097 cx.update(|_, cx| {
1098 provider.update(cx, |provider, _| {
1099 provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1100 id: None,
1101 edits: provider_edits,
1102 cursor_position: None,
1103 edit_preview: Some(edit_preview),
1104 }))
1105 })
1106 });
1107}
1108
1109fn propose_edits_with_cursor_position<T: ToOffset>(
1110 provider: &Entity<FakeEditPredictionDelegate>,
1111 edits: Vec<(Range<T>, &str)>,
1112 cursor_offset: Option<usize>,
1113 cx: &mut EditorTestContext,
1114) {
1115 let snapshot = cx.buffer_snapshot();
1116 let cursor_position = cursor_offset
1117 .map(|offset| PredictedCursorPosition::at_anchor(snapshot.anchor_after(offset)));
1118 let edits = edits.into_iter().map(|(range, text)| {
1119 let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1120 (range, text.into())
1121 });
1122
1123 cx.update(|_, cx| {
1124 provider.update(cx, |provider, _| {
1125 provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1126 id: None,
1127 edits: edits.collect(),
1128 cursor_position,
1129 edit_preview: None,
1130 }))
1131 })
1132 });
1133}
1134
1135fn propose_edits_with_cursor_position_in_insertion<T: ToOffset>(
1136 provider: &Entity<FakeEditPredictionDelegate>,
1137 edits: Vec<(Range<T>, &str)>,
1138 anchor_offset: usize,
1139 offset_within_insertion: usize,
1140 cx: &mut EditorTestContext,
1141) {
1142 let snapshot = cx.buffer_snapshot();
1143 // Use anchor_before (left bias) so the anchor stays at the insertion point
1144 // rather than moving past the inserted text
1145 let cursor_position = Some(PredictedCursorPosition::new(
1146 snapshot.anchor_before(anchor_offset),
1147 offset_within_insertion,
1148 ));
1149 let edits = edits.into_iter().map(|(range, text)| {
1150 let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1151 (range, text.into())
1152 });
1153
1154 cx.update(|_, cx| {
1155 provider.update(cx, |provider, _| {
1156 provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1157 id: None,
1158 edits: edits.collect(),
1159 cursor_position,
1160 edit_preview: None,
1161 }))
1162 })
1163 });
1164}
1165
1166fn assign_editor_completion_provider(
1167 provider: Entity<FakeEditPredictionDelegate>,
1168 cx: &mut EditorTestContext,
1169) {
1170 cx.update_editor(|editor, window, cx| {
1171 editor.set_edit_prediction_provider(Some(provider), window, cx);
1172 })
1173}
1174
1175fn assign_editor_completion_menu_provider(cx: &mut EditorTestContext) {
1176 cx.update_editor(|editor, _, _| {
1177 editor.set_completion_provider(Some(Rc::new(FakeCompletionMenuProvider)));
1178 });
1179}
1180
1181fn propose_edits_non_zed<T: ToOffset>(
1182 provider: &Entity<FakeNonZedEditPredictionDelegate>,
1183 edits: Vec<(Range<T>, &str)>,
1184 cx: &mut EditorTestContext,
1185) {
1186 let snapshot = cx.buffer_snapshot();
1187 let edits = edits.into_iter().map(|(range, text)| {
1188 let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
1189 (range, text.into())
1190 });
1191
1192 cx.update(|_, cx| {
1193 provider.update(cx, |provider, _| {
1194 provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
1195 id: None,
1196 edits: edits.collect(),
1197 cursor_position: None,
1198 edit_preview: None,
1199 }))
1200 })
1201 });
1202}
1203
1204fn assign_editor_completion_provider_non_zed(
1205 provider: Entity<FakeNonZedEditPredictionDelegate>,
1206 cx: &mut EditorTestContext,
1207) {
1208 cx.update_editor(|editor, window, cx| {
1209 editor.set_edit_prediction_provider(Some(provider), window, cx);
1210 })
1211}
1212
1213struct FakeCompletionMenuProvider;
1214
1215impl CompletionProvider for FakeCompletionMenuProvider {
1216 fn completions(
1217 &self,
1218 _excerpt_id: ExcerptId,
1219 _buffer: &Entity<Buffer>,
1220 _buffer_position: text::Anchor,
1221 _trigger: CompletionContext,
1222 _window: &mut Window,
1223 _cx: &mut Context<crate::Editor>,
1224 ) -> Task<anyhow::Result<Vec<CompletionResponse>>> {
1225 let completion = Completion {
1226 replace_range: text::Anchor::MIN..text::Anchor::MAX,
1227 new_text: "fake_completion".to_string(),
1228 label: CodeLabel::plain("fake_completion".to_string(), None),
1229 documentation: None,
1230 source: CompletionSource::Custom,
1231 icon_path: None,
1232 match_start: None,
1233 snippet_deduplication_key: None,
1234 insert_text_mode: None,
1235 confirm: None,
1236 };
1237
1238 Task::ready(Ok(vec![CompletionResponse {
1239 completions: vec![completion],
1240 display_options: Default::default(),
1241 is_incomplete: false,
1242 }]))
1243 }
1244
1245 fn is_completion_trigger(
1246 &self,
1247 _buffer: &Entity<Buffer>,
1248 _position: language::Anchor,
1249 _text: &str,
1250 _trigger_in_words: bool,
1251 _cx: &mut Context<crate::Editor>,
1252 ) -> bool {
1253 false
1254 }
1255
1256 fn filter_completions(&self) -> bool {
1257 false
1258 }
1259}
1260
1261#[derive(Default, Clone)]
1262pub struct FakeEditPredictionDelegate {
1263 pub completion: Option<edit_prediction_types::EditPrediction>,
1264 pub refresh_count: Arc<AtomicUsize>,
1265}
1266
1267impl FakeEditPredictionDelegate {
1268 pub fn set_edit_prediction(
1269 &mut self,
1270 completion: Option<edit_prediction_types::EditPrediction>,
1271 ) {
1272 self.completion = completion;
1273 }
1274}
1275
1276impl EditPredictionDelegate for FakeEditPredictionDelegate {
1277 fn name() -> &'static str {
1278 "fake-completion-provider"
1279 }
1280
1281 fn display_name() -> &'static str {
1282 "Fake Completion Provider"
1283 }
1284
1285 fn show_predictions_in_menu() -> bool {
1286 true
1287 }
1288
1289 fn supports_jump_to_edit() -> bool {
1290 true
1291 }
1292
1293 fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
1294 EditPredictionIconSet::new(IconName::ZedPredict)
1295 }
1296
1297 fn is_enabled(
1298 &self,
1299 _buffer: &gpui::Entity<language::Buffer>,
1300 _cursor_position: language::Anchor,
1301 _cx: &gpui::App,
1302 ) -> bool {
1303 true
1304 }
1305
1306 fn is_refreshing(&self, _cx: &gpui::App) -> bool {
1307 false
1308 }
1309
1310 fn refresh(
1311 &mut self,
1312 _buffer: gpui::Entity<language::Buffer>,
1313 _cursor_position: language::Anchor,
1314 _debounce: bool,
1315 _cx: &mut gpui::Context<Self>,
1316 ) {
1317 self.refresh_count.fetch_add(1, atomic::Ordering::SeqCst);
1318 }
1319
1320 fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
1321
1322 fn discard(
1323 &mut self,
1324 _reason: edit_prediction_types::EditPredictionDiscardReason,
1325 _cx: &mut gpui::Context<Self>,
1326 ) {
1327 }
1328
1329 fn suggest<'a>(
1330 &mut self,
1331 _buffer: &gpui::Entity<language::Buffer>,
1332 _cursor_position: language::Anchor,
1333 _cx: &mut gpui::Context<Self>,
1334 ) -> Option<edit_prediction_types::EditPrediction> {
1335 self.completion.clone()
1336 }
1337}
1338
1339#[derive(Default, Clone)]
1340pub struct FakeNonZedEditPredictionDelegate {
1341 pub completion: Option<edit_prediction_types::EditPrediction>,
1342}
1343
1344impl FakeNonZedEditPredictionDelegate {
1345 pub fn set_edit_prediction(
1346 &mut self,
1347 completion: Option<edit_prediction_types::EditPrediction>,
1348 ) {
1349 self.completion = completion;
1350 }
1351}
1352
1353impl EditPredictionDelegate for FakeNonZedEditPredictionDelegate {
1354 fn name() -> &'static str {
1355 "fake-non-zed-provider"
1356 }
1357
1358 fn display_name() -> &'static str {
1359 "Fake Non-Zed Provider"
1360 }
1361
1362 fn show_predictions_in_menu() -> bool {
1363 false
1364 }
1365
1366 fn supports_jump_to_edit() -> bool {
1367 false
1368 }
1369
1370 fn icons(&self, _cx: &gpui::App) -> EditPredictionIconSet {
1371 EditPredictionIconSet::new(IconName::ZedPredict)
1372 }
1373
1374 fn is_enabled(
1375 &self,
1376 _buffer: &gpui::Entity<language::Buffer>,
1377 _cursor_position: language::Anchor,
1378 _cx: &gpui::App,
1379 ) -> bool {
1380 true
1381 }
1382
1383 fn is_refreshing(&self, _cx: &gpui::App) -> bool {
1384 false
1385 }
1386
1387 fn refresh(
1388 &mut self,
1389 _buffer: gpui::Entity<language::Buffer>,
1390 _cursor_position: language::Anchor,
1391 _debounce: bool,
1392 _cx: &mut gpui::Context<Self>,
1393 ) {
1394 }
1395
1396 fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
1397
1398 fn discard(
1399 &mut self,
1400 _reason: edit_prediction_types::EditPredictionDiscardReason,
1401 _cx: &mut gpui::Context<Self>,
1402 ) {
1403 }
1404
1405 fn suggest<'a>(
1406 &mut self,
1407 _buffer: &gpui::Entity<language::Buffer>,
1408 _cursor_position: language::Anchor,
1409 _cx: &mut gpui::Context<Self>,
1410 ) -> Option<edit_prediction_types::EditPrediction> {
1411 self.completion.clone()
1412 }
1413}