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