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