Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:33:07.507Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

edit_prediction.rs

2599 lines · 97.9 KB · rust
1use super::*;
2
3pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
4    EditPredictionStyles {
5        insertion: HighlightStyle {
6            color: Some(cx.theme().status().predictive),
7            ..HighlightStyle::default()
8        },
9        whitespace: HighlightStyle {
10            background_color: Some(cx.theme().status().created_background),
11            ..HighlightStyle::default()
12        },
13    }
14}
15
16pub(super) enum EditDisplayMode {
17    TabAccept,
18    DiffPopover,
19    Inline,
20}
21
22pub(super) enum EditPrediction {
23    Edit {
24        // TODO could be a language::Anchor?
25        edits: Vec<(Range<Anchor>, Arc<str>)>,
26        /// Predicted cursor position as (anchor, offset_from_anchor).
27        /// The anchor is in multibuffer coordinates; after applying edits,
28        /// resolve the anchor and add the offset to get the final cursor position.
29        cursor_position: Option<(Anchor, usize)>,
30        edit_preview: Option<EditPreview>,
31        display_mode: EditDisplayMode,
32        snapshot: BufferSnapshot,
33    },
34    /// Move to a specific location in the active editor
35    MoveWithin {
36        target: Anchor,
37        snapshot: BufferSnapshot,
38    },
39    /// Move to a specific location in a different editor (not the active one)
40    MoveOutside {
41        target: language::Anchor,
42        snapshot: BufferSnapshot,
43    },
44}
45
46pub(super) struct EditPredictionState {
47    pub(super) inlay_ids: Vec<InlayId>,
48    pub(super) completion: EditPrediction,
49    pub(super) completion_id: Option<SharedString>,
50    pub(super) invalidation_range: Option<Range<Anchor>>,
51}
52
53pub(super) enum EditPredictionSettings {
54    Disabled,
55    Enabled {
56        show_in_menu: bool,
57        preview_requires_modifier: bool,
58    },
59}
60
61pub(super) enum MenuEditPredictionsPolicy {
62    #[cfg(test)]
63    Never,
64    ByProvider,
65}
66
67pub(super) enum EditPredictionPreview {
68    /// Modifier is not pressed
69    Inactive { released_too_fast: bool },
70    /// Modifier pressed
71    Active {
72        since: Instant,
73        previous_scroll_position: Option<SharedScrollAnchor>,
74    },
75}
76
77#[derive(Copy, Clone, Eq, PartialEq)]
78pub(super) enum EditPredictionKeybindSurface {
79    Inline,
80    CursorPopoverCompact,
81    CursorPopoverExpanded,
82}
83
84#[derive(Copy, Clone, Eq, PartialEq, Debug)]
85pub(super) enum EditPredictionKeybindAction {
86    Accept,
87    Preview,
88}
89
90pub(super) struct EditPredictionKeybindDisplay {
91    #[cfg(test)]
92    pub(super) accept_keystroke: Option<gpui::KeybindingKeystroke>,
93    #[cfg(test)]
94    pub(super) preview_keystroke: Option<gpui::KeybindingKeystroke>,
95    pub(super) displayed_keystroke: Option<gpui::KeybindingKeystroke>,
96    pub(super) action: EditPredictionKeybindAction,
97    pub(super) missing_accept_keystroke: bool,
98    pub(super) show_hold_label: bool,
99}
100
101impl EditPredictionPreview {
102    pub(super) fn released_too_fast(&self) -> bool {
103        match self {
104            EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
105            EditPredictionPreview::Active { .. } => false,
106        }
107    }
108
109    pub(super) fn set_previous_scroll_position(
110        &mut self,
111        scroll_position: Option<SharedScrollAnchor>,
112    ) {
113        if let EditPredictionPreview::Active {
114            previous_scroll_position,
115            ..
116        } = self
117        {
118            *previous_scroll_position = scroll_position;
119        }
120    }
121}
122
123pub(super) struct RegisteredEditPredictionDelegate {
124    pub(super) provider: Arc<dyn EditPredictionDelegateHandle>,
125    _subscription: Subscription,
126}
127
128pub(super) fn edit_prediction_edit_text(
129    current_snapshot: &BufferSnapshot,
130    edits: &[(Range<Anchor>, impl AsRef<str>)],
131    edit_preview: &EditPreview,
132    include_deletions: bool,
133    multibuffer_snapshot: &MultiBufferSnapshot,
134    cx: &App,
135) -> HighlightedText {
136    let edits = edits
137        .iter()
138        .filter_map(|(anchor, text)| {
139            Some((
140                multibuffer_snapshot
141                    .anchor_range_to_buffer_anchor_range(anchor.clone())?
142                    .1,
143                text,
144            ))
145        })
146        .collect::<Vec<_>>();
147
148    edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
149}
150
151impl Editor {
152    pub fn set_edit_prediction_provider<T>(
153        &mut self,
154        provider: Option<Entity<T>>,
155        trigger: EditPredictionRequestTrigger,
156        window: &mut Window,
157        cx: &mut Context<Self>,
158    ) where
159        T: EditPredictionDelegate,
160    {
161        self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
162            _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
163                if this.focus_handle.is_focused(window) {
164                    this.update_visible_edit_prediction(window, cx);
165                }
166            }),
167            provider: Arc::new(provider),
168        });
169        self.update_edit_prediction_settings(cx);
170        self.refresh_edit_prediction(false, false, trigger, window, cx);
171    }
172
173    pub fn set_edit_predictions_hidden_for_vim_mode(
174        &mut self,
175        hidden: bool,
176        window: &mut Window,
177        cx: &mut Context<Self>,
178    ) {
179        if hidden != self.edit_predictions_hidden_for_vim_mode {
180            self.edit_predictions_hidden_for_vim_mode = hidden;
181            if hidden {
182                self.update_visible_edit_prediction(window, cx);
183            } else {
184                self.refresh_edit_prediction(
185                    true,
186                    false,
187                    EditPredictionRequestTrigger::VimModeChanged,
188                    window,
189                    cx,
190                );
191            }
192        }
193    }
194
195    pub fn toggle_edit_predictions(
196        &mut self,
197        _: &ToggleEditPrediction,
198        window: &mut Window,
199        cx: &mut Context<Self>,
200    ) {
201        if self.show_edit_predictions_override.is_some() {
202            self.set_show_edit_predictions(None, window, cx);
203        } else {
204            let show_edit_predictions = !self.edit_predictions_enabled();
205            self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
206        }
207    }
208
209    pub fn set_show_edit_predictions(
210        &mut self,
211        show_edit_predictions: Option<bool>,
212        window: &mut Window,
213        cx: &mut Context<Self>,
214    ) {
215        self.show_edit_predictions_override = show_edit_predictions;
216        self.update_edit_prediction_settings(cx);
217
218        if let Some(false) = show_edit_predictions {
219            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
220        } else {
221            self.refresh_edit_prediction(
222                false,
223                true,
224                EditPredictionRequestTrigger::Explicit,
225                window,
226                cx,
227            );
228        }
229    }
230
231    pub fn refresh_edit_prediction(
232        &mut self,
233        debounce: bool,
234        user_requested: bool,
235        trigger: EditPredictionRequestTrigger,
236        window: &mut Window,
237        cx: &mut Context<Self>,
238    ) -> Option<()> {
239        if self.leader_id.is_some() {
240            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
241            return None;
242        }
243
244        let cursor = self.selections.newest_anchor().head();
245        let (buffer, cursor_buffer_position) =
246            self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
247
248        if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
249            return None;
250        }
251
252        if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
253            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
254            return None;
255        }
256
257        self.update_visible_edit_prediction(window, cx);
258
259        if !user_requested
260            && (!self.should_show_edit_predictions()
261                || !self.is_focused(window)
262                || buffer.read(cx).is_empty())
263        {
264            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
265            return None;
266        }
267
268        self.edit_prediction_provider()?.refresh(
269            buffer,
270            cursor_buffer_position,
271            debounce,
272            trigger,
273            cx,
274        );
275        Some(())
276    }
277
278    pub fn edit_predictions_enabled(&self) -> bool {
279        match self.edit_prediction_settings {
280            EditPredictionSettings::Disabled => false,
281            EditPredictionSettings::Enabled { .. } => true,
282        }
283    }
284
285    pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
286        if self.edit_prediction_provider.is_none() {
287            self.edit_prediction_settings = EditPredictionSettings::Disabled;
288            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
289            return;
290        }
291
292        let selection = self.selections.newest_anchor();
293        let cursor = selection.head();
294
295        if let Some((buffer, cursor_buffer_position)) =
296            self.buffer.read(cx).text_anchor_for_position(cursor, cx)
297        {
298            if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
299                self.edit_prediction_settings = EditPredictionSettings::Disabled;
300                self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
301                return;
302            }
303            self.edit_prediction_settings =
304                self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
305        }
306    }
307
308    pub fn edit_prediction_preview_is_active(&self) -> bool {
309        matches!(
310            self.edit_prediction_preview,
311            EditPredictionPreview::Active { .. }
312        )
313    }
314
315    pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
316        let cursor = self.selections.newest_anchor().head();
317        if let Some((buffer, cursor_position)) =
318            self.buffer.read(cx).text_anchor_for_position(cursor, cx)
319        {
320            self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
321        } else {
322            false
323        }
324    }
325
326    pub fn show_edit_prediction(
327        &mut self,
328        _: &ShowEditPrediction,
329        window: &mut Window,
330        cx: &mut Context<Self>,
331    ) {
332        if !self.has_active_edit_prediction() {
333            self.refresh_edit_prediction(
334                false,
335                true,
336                EditPredictionRequestTrigger::Explicit,
337                window,
338                cx,
339            );
340            return;
341        }
342
343        self.update_visible_edit_prediction(window, cx);
344    }
345
346    pub fn accept_partial_edit_prediction(
347        &mut self,
348        granularity: EditPredictionGranularity,
349        window: &mut Window,
350        cx: &mut Context<Self>,
351    ) {
352        if self.read_only(cx) {
353            return;
354        }
355        if self.show_edit_predictions_in_menu() {
356            self.hide_context_menu(window, cx);
357        }
358
359        let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
360            return;
361        };
362
363        if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
364            return;
365        }
366
367        match &active_edit_prediction.completion {
368            EditPrediction::MoveWithin { target, .. } => {
369                let target = *target;
370
371                if matches!(granularity, EditPredictionGranularity::Full) {
372                    if let Some(position_map) = &self.last_position_map {
373                        let target_row = target.to_display_point(&position_map.snapshot).row();
374                        let is_visible = position_map.visible_row_range.contains(&target_row);
375
376                        if is_visible || !self.edit_prediction_requires_modifier() {
377                            self.unfold_ranges(&[target..target], true, false, cx);
378                            self.change_selections(
379                                SelectionEffects::scroll(Autoscroll::newest()),
380                                window,
381                                cx,
382                                |selections| {
383                                    selections.select_anchor_ranges([target..target]);
384                                },
385                            );
386                            self.clear_row_highlights::<EditPredictionPreview>();
387                            self.edit_prediction_preview
388                                .set_previous_scroll_position(None);
389                        } else {
390                            // Highlight and request scroll
391                            self.edit_prediction_preview
392                                .set_previous_scroll_position(Some(
393                                    position_map.snapshot.scroll_anchor,
394                                ));
395                            self.highlight_rows::<EditPredictionPreview>(
396                                target..target,
397                                |cx| cx.theme().colors().editor_highlighted_line_background,
398                                RowHighlightOptions {
399                                    autoscroll: true,
400                                    ..Default::default()
401                                },
402                                cx,
403                            );
404                            self.request_autoscroll(Autoscroll::fit(), cx);
405                        }
406                    }
407                } else {
408                    self.change_selections(
409                        SelectionEffects::scroll(Autoscroll::newest()),
410                        window,
411                        cx,
412                        |selections| {
413                            selections.select_anchor_ranges([target..target]);
414                        },
415                    );
416                }
417            }
418            EditPrediction::MoveOutside { snapshot, target } => {
419                if let Some(workspace) = self.workspace() {
420                    Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
421                        .detach_and_log_err(cx);
422                }
423            }
424            EditPrediction::Edit {
425                edits,
426                cursor_position,
427                ..
428            } => {
429                self.report_edit_prediction_event(
430                    active_edit_prediction.completion_id.clone(),
431                    true,
432                    cx,
433                );
434
435                match granularity {
436                    EditPredictionGranularity::Full => {
437                        let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
438
439                        // Compute fallback cursor position BEFORE applying the edit,
440                        // so the anchor tracks through the edit correctly
441                        let fallback_cursor_target = {
442                            let snapshot = self.buffer.read(cx).snapshot(cx);
443                            let Some((last_edit_range, _)) = edits.last() else {
444                                return;
445                            };
446                            last_edit_range.end.bias_right(&snapshot)
447                        };
448
449                        self.buffer.update(cx, |buffer, cx| {
450                            buffer.edit(edits.iter().cloned(), None, cx)
451                        });
452
453                        if let Some(provider) = self.edit_prediction_provider() {
454                            provider.accept(cx);
455                        }
456
457                        // Resolve cursor position after the edit is applied
458                        let cursor_target = if let Some((anchor, offset)) = cursor_position {
459                            // The anchor tracks through the edit, then we add the offset
460                            let snapshot = self.buffer.read(cx).snapshot(cx);
461                            let base_offset = anchor.to_offset(&snapshot).0;
462                            let target_offset =
463                                MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
464                            snapshot.anchor_after(target_offset)
465                        } else {
466                            fallback_cursor_target
467                        };
468
469                        self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
470                            s.select_anchor_ranges([cursor_target..cursor_target]);
471                        });
472
473                        let selections = self.selections.disjoint_anchors_arc();
474                        if let Some(transaction_id_now) =
475                            self.buffer.read(cx).last_transaction_id(cx)
476                        {
477                            if transaction_id_prev != Some(transaction_id_now) {
478                                self.selection_history
479                                    .insert_transaction(transaction_id_now, selections);
480                            }
481                        }
482
483                        self.update_visible_edit_prediction(window, cx);
484                        if self.active_edit_prediction.is_none() {
485                            self.refresh_edit_prediction(
486                                true,
487                                true,
488                                EditPredictionRequestTrigger::PredictionAccepted,
489                                window,
490                                cx,
491                            );
492                        }
493                        cx.notify();
494                    }
495                    _ => {
496                        let snapshot = self.buffer.read(cx).snapshot(cx);
497                        let cursor_offset = self
498                            .selections
499                            .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
500                            .head();
501
502                        let insertion = edits.iter().find_map(|(range, text)| {
503                            let range = range.to_offset(&snapshot);
504                            if range.is_empty() && range.start == cursor_offset {
505                                Some(text)
506                            } else {
507                                None
508                            }
509                        });
510
511                        if let Some(text) = insertion {
512                            let text_to_insert = match granularity {
513                                EditPredictionGranularity::Word => {
514                                    let mut partial = text
515                                        .chars()
516                                        .by_ref()
517                                        .take_while(|c| c.is_alphabetic())
518                                        .collect::<String>();
519                                    if partial.is_empty() {
520                                        partial = text
521                                            .chars()
522                                            .by_ref()
523                                            .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
524                                            .collect::<String>();
525                                    }
526                                    partial
527                                }
528                                EditPredictionGranularity::Line => {
529                                    if let Some(line) = text.split_inclusive('\n').next() {
530                                        line.to_string()
531                                    } else {
532                                        text.to_string()
533                                    }
534                                }
535                                EditPredictionGranularity::Full => unreachable!(),
536                            };
537
538                            cx.emit(EditorEvent::InputHandled {
539                                utf16_range_to_replace: None,
540                                text: text_to_insert.clone().into(),
541                            });
542
543                            self.replace_selections(&text_to_insert, None, window, cx, false);
544                            self.refresh_edit_prediction(
545                                true,
546                                true,
547                                EditPredictionRequestTrigger::PredictionPartiallyAccepted,
548                                window,
549                                cx,
550                            );
551                            cx.notify();
552                        } else {
553                            self.accept_partial_edit_prediction(
554                                EditPredictionGranularity::Full,
555                                window,
556                                cx,
557                            );
558                        }
559                    }
560                }
561            }
562        }
563    }
564
565    pub fn accept_next_word_edit_prediction(
566        &mut self,
567        _: &AcceptNextWordEditPrediction,
568        window: &mut Window,
569        cx: &mut Context<Self>,
570    ) {
571        self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
572    }
573
574    pub fn accept_next_line_edit_prediction(
575        &mut self,
576        _: &AcceptNextLineEditPrediction,
577        window: &mut Window,
578        cx: &mut Context<Self>,
579    ) {
580        self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
581    }
582
583    pub fn accept_edit_prediction(
584        &mut self,
585        _: &AcceptEditPrediction,
586        window: &mut Window,
587        cx: &mut Context<Self>,
588    ) {
589        self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
590    }
591
592    pub fn has_active_edit_prediction(&self) -> bool {
593        self.active_edit_prediction.is_some()
594    }
595
596    /// Returns true when we're displaying the edit prediction popover below the cursor
597    /// like we are not previewing and the LSP autocomplete menu is visible
598    /// or we are in `when_holding_modifier` mode.
599    pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
600        if self.edit_prediction_preview_is_active()
601            || !self.show_edit_predictions_in_menu()
602            || !self.edit_predictions_enabled()
603        {
604            return false;
605        }
606
607        if self.has_visible_completions_menu() {
608            return true;
609        }
610
611        has_completion && self.edit_prediction_requires_modifier()
612    }
613
614    pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
615        Some(self.edit_prediction_provider.as_ref()?.provider.clone())
616    }
617
618    pub(super) fn preview_edit_prediction_keystroke(
619        &self,
620        window: &mut Window,
621        cx: &mut App,
622    ) -> Option<gpui::KeybindingKeystroke> {
623        let key_context = self.key_context_internal(true, window, cx);
624        let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
625        bindings
626            .into_iter()
627            .rev()
628            .find_map(|binding| match binding.keystrokes() {
629                [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
630                _ => None,
631            })
632    }
633
634    pub(super) fn edit_prediction_keybind_display(
635        &self,
636        surface: EditPredictionKeybindSurface,
637        window: &mut Window,
638        cx: &mut App,
639    ) -> EditPredictionKeybindDisplay {
640        let accept_keystroke =
641            self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
642        let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
643
644        let action = match surface {
645            EditPredictionKeybindSurface::Inline
646            | EditPredictionKeybindSurface::CursorPopoverCompact => {
647                if self.edit_prediction_requires_modifier() {
648                    EditPredictionKeybindAction::Preview
649                } else {
650                    EditPredictionKeybindAction::Accept
651                }
652            }
653            EditPredictionKeybindSurface::CursorPopoverExpanded => self
654                .active_edit_prediction
655                .as_ref()
656                .filter(|completion| {
657                    self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
658                })
659                .map_or(EditPredictionKeybindAction::Accept, |_| {
660                    EditPredictionKeybindAction::Preview
661                }),
662        };
663        #[cfg(test)]
664        let preview_copy = preview_keystroke.clone();
665        #[cfg(test)]
666        let accept_copy = accept_keystroke.clone();
667
668        let displayed_keystroke = match surface {
669            EditPredictionKeybindSurface::Inline => match action {
670                EditPredictionKeybindAction::Accept => accept_keystroke,
671                EditPredictionKeybindAction::Preview => preview_keystroke,
672            },
673            EditPredictionKeybindSurface::CursorPopoverCompact
674            | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
675                EditPredictionKeybindAction::Accept => accept_keystroke,
676                EditPredictionKeybindAction::Preview => {
677                    preview_keystroke.or_else(|| accept_keystroke.clone())
678                }
679            },
680        };
681
682        let missing_accept_keystroke = displayed_keystroke.is_none();
683
684        EditPredictionKeybindDisplay {
685            #[cfg(test)]
686            accept_keystroke: accept_copy,
687            #[cfg(test)]
688            preview_keystroke: preview_copy,
689            displayed_keystroke,
690            action,
691            missing_accept_keystroke,
692            show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
693                && self.edit_prediction_preview.released_too_fast(),
694        }
695    }
696
697    pub(super) fn show_edit_predictions_in_menu(&self) -> bool {
698        match self.edit_prediction_settings {
699            EditPredictionSettings::Disabled => false,
700            EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
701        }
702    }
703
704    pub(super) fn edit_prediction_requires_modifier(&self) -> bool {
705        match self.edit_prediction_settings {
706            EditPredictionSettings::Disabled => false,
707            EditPredictionSettings::Enabled {
708                preview_requires_modifier,
709                ..
710            } => preview_requires_modifier,
711        }
712    }
713
714    pub(super) fn discard_edit_prediction(
715        &mut self,
716        reason: EditPredictionDiscardReason,
717        cx: &mut Context<Self>,
718    ) -> bool {
719        if reason == EditPredictionDiscardReason::Rejected {
720            let completion_id = self
721                .active_edit_prediction
722                .as_ref()
723                .and_then(|active_completion| active_completion.completion_id.clone());
724
725            self.report_edit_prediction_event(completion_id, false, cx);
726        }
727
728        if let Some(provider) = self.edit_prediction_provider() {
729            provider.discard(reason, cx);
730        }
731
732        self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
733    }
734
735    pub(super) fn take_active_edit_prediction(
736        &mut self,
737        preserve_stale_in_menu: bool,
738        cx: &mut Context<Self>,
739    ) -> bool {
740        let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
741            if !preserve_stale_in_menu {
742                self.stale_edit_prediction_in_menu = None;
743            }
744            return false;
745        };
746
747        self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
748        self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
749        self.stale_edit_prediction_in_menu =
750            preserve_stale_in_menu.then_some(active_edit_prediction);
751        true
752    }
753
754    pub(super) fn update_edit_prediction_preview(
755        &mut self,
756        modifiers: &Modifiers,
757        window: &mut Window,
758        cx: &mut Context<Self>,
759    ) {
760        let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
761
762        if modifiers_held {
763            if matches!(
764                self.edit_prediction_preview,
765                EditPredictionPreview::Inactive { .. }
766            ) {
767                self.edit_prediction_preview = EditPredictionPreview::Active {
768                    previous_scroll_position: None,
769                    since: Instant::now(),
770                };
771
772                self.update_visible_edit_prediction(window, cx);
773                cx.notify();
774            }
775        } else if let EditPredictionPreview::Active {
776            previous_scroll_position,
777            since,
778        } = self.edit_prediction_preview
779        {
780            if let (Some(previous_scroll_position), Some(position_map)) =
781                (previous_scroll_position, self.last_position_map.as_ref())
782            {
783                self.set_scroll_position(
784                    previous_scroll_position
785                        .scroll_position(&position_map.snapshot.display_snapshot),
786                    window,
787                    cx,
788                );
789            }
790
791            self.edit_prediction_preview = EditPredictionPreview::Inactive {
792                released_too_fast: since.elapsed() < Duration::from_millis(200),
793            };
794            self.clear_row_highlights::<EditPredictionPreview>();
795            self.update_visible_edit_prediction(window, cx);
796            cx.notify();
797        }
798    }
799
800    pub(super) fn update_visible_edit_prediction(
801        &mut self,
802        _window: &mut Window,
803        cx: &mut Context<Self>,
804    ) -> Option<()> {
805        if self.ime_transaction.is_some() {
806            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
807            return None;
808        }
809
810        let selection = self.selections.newest_anchor();
811        let multibuffer = self.buffer.read(cx).snapshot(cx);
812        let cursor = selection.head();
813        let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
814        let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
815
816        // Check project-level disable_ai setting for the current buffer
817        if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
818            return None;
819        }
820        let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
821
822        let show_in_menu = self.show_edit_predictions_in_menu();
823        let completions_menu_has_precedence = !show_in_menu
824            && (self.context_menu.borrow().is_some()
825                || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
826
827        if completions_menu_has_precedence
828            || !offset_selection.is_empty()
829            || self
830                .active_edit_prediction
831                .as_ref()
832                .is_some_and(|completion| {
833                    let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
834                        return false;
835                    };
836                    let invalidation_range = invalidation_range.to_offset(&multibuffer);
837                    let invalidation_range = invalidation_range.start..=invalidation_range.end;
838                    !invalidation_range.contains(&offset_selection.head())
839                })
840        {
841            self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
842            return None;
843        }
844
845        self.take_active_edit_prediction(true, cx);
846        let Some(provider) = self.edit_prediction_provider() else {
847            self.edit_prediction_settings = EditPredictionSettings::Disabled;
848            return None;
849        };
850
851        self.edit_prediction_settings =
852            self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx);
853
854        self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
855
856        if self.in_leading_whitespace {
857            let cursor_point = cursor.to_point(&multibuffer);
858            let mut suggested_indent = None;
859            multibuffer.suggested_indents_callback(
860                cursor_point.row..cursor_point.row + 1,
861                &mut |_, indent| {
862                    suggested_indent = Some(indent);
863                    ControlFlow::Break(())
864                },
865                cx,
866            );
867
868            if let Some(indent) = suggested_indent
869                && indent.len == cursor_point.column
870            {
871                self.in_leading_whitespace = false;
872            }
873        }
874
875        let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?;
876
877        let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
878        {
879            edit_prediction_types::EditPrediction::Local {
880                id,
881                edits,
882                cursor_position,
883                edit_preview,
884            } => (id, edits, cursor_position, edit_preview),
885            edit_prediction_types::EditPrediction::Jump {
886                id,
887                snapshot,
888                target,
889            } => {
890                if let Some(provider) = &self.edit_prediction_provider {
891                    provider.provider.did_show(SuggestionDisplayType::Jump, cx);
892                }
893                self.stale_edit_prediction_in_menu = None;
894                self.active_edit_prediction = Some(EditPredictionState {
895                    inlay_ids: vec![],
896                    completion: EditPrediction::MoveOutside { snapshot, target },
897                    completion_id: id,
898                    invalidation_range: None,
899                });
900                cx.notify();
901                return Some(());
902            }
903        };
904
905        let edits = edits
906            .into_iter()
907            .flat_map(|(range, new_text)| {
908                Some((
909                    multibuffer.buffer_anchor_range_to_anchor_range(range)?,
910                    new_text,
911                ))
912            })
913            .collect::<Vec<_>>();
914        if edits.is_empty() {
915            return None;
916        }
917
918        let cursor_position = predicted_cursor_position.and_then(|predicted| {
919            let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?;
920            Some((anchor, predicted.offset))
921        });
922
923        let Some((first_edit_range, _)) = edits.first() else {
924            return None;
925        };
926        let Some((last_edit_range, _)) = edits.last() else {
927            return None;
928        };
929
930        let first_edit_start = first_edit_range.start;
931        let first_edit_start_point = first_edit_start.to_point(&multibuffer);
932        let edit_start_row = first_edit_start_point.row.saturating_sub(2);
933
934        let last_edit_end = last_edit_range.end;
935        let last_edit_end_point = last_edit_end.to_point(&multibuffer);
936        let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
937
938        let cursor_row = cursor.to_point(&multibuffer).row;
939
940        let snapshot = multibuffer
941            .buffer_for_id(cursor_text_anchor.buffer_id)
942            .cloned()?;
943
944        let mut inlay_ids = Vec::new();
945        let invalidation_row_range;
946        let move_invalidation_row_range = if cursor_row < edit_start_row {
947            Some(cursor_row..edit_end_row)
948        } else if cursor_row > edit_end_row {
949            Some(edit_start_row..cursor_row)
950        } else {
951            None
952        };
953        let supports_jump = self
954            .edit_prediction_provider
955            .as_ref()
956            .map(|provider| provider.provider.supports_jump_to_edit())
957            .unwrap_or(true);
958
959        let is_move = supports_jump
960            && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
961        let completion = if is_move {
962            if let Some(provider) = &self.edit_prediction_provider {
963                provider.provider.did_show(SuggestionDisplayType::Jump, cx);
964            }
965            invalidation_row_range =
966                move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
967
968            let (_, snapshot) = multibuffer.anchor_to_buffer_anchor(first_edit_start)?;
969
970            EditPrediction::MoveWithin {
971                target: first_edit_start,
972                snapshot: snapshot.clone(),
973            }
974        } else {
975            let show_completions_in_menu = self.has_visible_completions_menu();
976            let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
977                && !self.edit_predictions_hidden_for_vim_mode;
978
979            let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
980                if provider.show_tab_accept_marker() {
981                    EditDisplayMode::TabAccept
982                } else {
983                    EditDisplayMode::Inline
984                }
985            } else {
986                EditDisplayMode::DiffPopover
987            };
988
989            let report_shown = match display_mode {
990                EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
991                    show_completions_in_buffer || show_completions_in_menu
992                }
993                EditDisplayMode::TabAccept => {
994                    show_completions_in_menu || self.edit_prediction_preview_is_active()
995                }
996            };
997
998            if report_shown && let Some(provider) = &self.edit_prediction_provider {
999                let suggestion_display_type = match display_mode {
1000                    EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
1001                    EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
1002                        SuggestionDisplayType::GhostText
1003                    }
1004                };
1005                provider.provider.did_show(suggestion_display_type, cx);
1006            }
1007
1008            if show_completions_in_buffer {
1009                if edits
1010                    .iter()
1011                    .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
1012                {
1013                    let mut inlays = Vec::new();
1014                    for (range, new_text) in &edits {
1015                        let inlay = Inlay::edit_prediction(
1016                            post_inc(&mut self.next_inlay_id),
1017                            range.start,
1018                            new_text.as_ref(),
1019                        );
1020                        inlay_ids.push(inlay.id);
1021                        inlays.push(inlay);
1022                    }
1023
1024                    self.splice_inlays(&[], inlays, cx);
1025                } else {
1026                    let background_color = cx.theme().status().deleted_background;
1027                    self.highlight_text(
1028                        HighlightKey::EditPredictionHighlight,
1029                        edits.iter().map(|(range, _)| range.clone()).collect(),
1030                        HighlightStyle {
1031                            background_color: Some(background_color),
1032                            ..Default::default()
1033                        },
1034                        cx,
1035                    );
1036                }
1037            }
1038
1039            invalidation_row_range = edit_start_row..edit_end_row;
1040
1041            EditPrediction::Edit {
1042                edits,
1043                cursor_position,
1044                edit_preview,
1045                display_mode,
1046                snapshot,
1047            }
1048        };
1049
1050        let invalidation_range = multibuffer
1051            .anchor_before(Point::new(invalidation_row_range.start, 0))
1052            ..multibuffer.anchor_after(Point::new(
1053                invalidation_row_range.end,
1054                multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
1055            ));
1056
1057        self.stale_edit_prediction_in_menu = None;
1058        self.active_edit_prediction = Some(EditPredictionState {
1059            inlay_ids,
1060            completion,
1061            completion_id,
1062            invalidation_range: Some(invalidation_range),
1063        });
1064
1065        cx.notify();
1066
1067        Some(())
1068    }
1069
1070    pub(super) fn render_edit_prediction_popover(
1071        &mut self,
1072        text_bounds: &Bounds<Pixels>,
1073        content_origin: gpui::Point<Pixels>,
1074        right_margin: Pixels,
1075        editor_snapshot: &EditorSnapshot,
1076        visible_row_range: Range<DisplayRow>,
1077        scroll_top: ScrollOffset,
1078        scroll_bottom: ScrollOffset,
1079        line_layouts: &[LineWithInvisibles],
1080        line_height: Pixels,
1081        scroll_position: gpui::Point<ScrollOffset>,
1082        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
1083        newest_selection_head: Option<DisplayPoint>,
1084        editor_width: Pixels,
1085        style: &EditorStyle,
1086        window: &mut Window,
1087        cx: &mut App,
1088    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1089        if self.mode().is_minimap() {
1090            return None;
1091        }
1092        let active_edit_prediction = self.active_edit_prediction.as_ref()?;
1093
1094        if self.edit_prediction_visible_in_cursor_popover(true) {
1095            return None;
1096        }
1097
1098        match &active_edit_prediction.completion {
1099            EditPrediction::MoveWithin { target, .. } => {
1100                let target_display_point = target.to_display_point(editor_snapshot);
1101
1102                if self.edit_prediction_requires_modifier() {
1103                    if !self.edit_prediction_preview_is_active() {
1104                        return None;
1105                    }
1106
1107                    self.render_edit_prediction_modifier_jump_popover(
1108                        text_bounds,
1109                        content_origin,
1110                        visible_row_range,
1111                        line_layouts,
1112                        line_height,
1113                        scroll_pixel_position,
1114                        newest_selection_head,
1115                        target_display_point,
1116                        window,
1117                        cx,
1118                    )
1119                } else {
1120                    self.render_edit_prediction_eager_jump_popover(
1121                        text_bounds,
1122                        content_origin,
1123                        editor_snapshot,
1124                        visible_row_range,
1125                        scroll_top,
1126                        scroll_bottom,
1127                        line_height,
1128                        scroll_pixel_position,
1129                        target_display_point,
1130                        editor_width,
1131                        window,
1132                        cx,
1133                    )
1134                }
1135            }
1136            EditPrediction::Edit {
1137                display_mode: EditDisplayMode::Inline,
1138                ..
1139            } => None,
1140            EditPrediction::Edit {
1141                display_mode: EditDisplayMode::TabAccept,
1142                edits,
1143                ..
1144            } => {
1145                let range = &edits.first()?.0;
1146                let target_display_point = range.end.to_display_point(editor_snapshot);
1147
1148                self.render_edit_prediction_end_of_line_popover(
1149                    "Accept",
1150                    editor_snapshot,
1151                    visible_row_range,
1152                    target_display_point,
1153                    line_height,
1154                    scroll_pixel_position,
1155                    content_origin,
1156                    editor_width,
1157                    window,
1158                    cx,
1159                )
1160            }
1161            EditPrediction::Edit {
1162                edits,
1163                edit_preview,
1164                display_mode: EditDisplayMode::DiffPopover,
1165                snapshot,
1166                ..
1167            } => self.render_edit_prediction_diff_popover(
1168                text_bounds,
1169                content_origin,
1170                right_margin,
1171                editor_snapshot,
1172                visible_row_range,
1173                line_layouts,
1174                line_height,
1175                scroll_position,
1176                scroll_pixel_position,
1177                newest_selection_head,
1178                editor_width,
1179                style,
1180                edits,
1181                edit_preview,
1182                snapshot,
1183                window,
1184                cx,
1185            ),
1186            EditPrediction::MoveOutside { snapshot, .. } => {
1187                let mut element = self
1188                    .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
1189                    .into_any();
1190
1191                let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1192                let origin_x = text_bounds.size.width - size.width - px(30.);
1193                let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
1194                element.prepaint_at(origin, window, cx);
1195
1196                Some((element, origin))
1197            }
1198        }
1199    }
1200
1201    pub(super) fn edit_prediction_cursor_popover_height(&self) -> Pixels {
1202        px(30.)
1203    }
1204
1205    pub(super) fn render_edit_prediction_cursor_popover(
1206        &self,
1207        min_width: Pixels,
1208        max_width: Pixels,
1209        cursor_point: Point,
1210        style: &EditorStyle,
1211        window: &mut Window,
1212        cx: &mut Context<Editor>,
1213    ) -> Option<AnyElement> {
1214        let provider = self.edit_prediction_provider.as_ref()?;
1215        let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
1216
1217        let is_refreshing = provider.provider.is_refreshing(cx);
1218
1219        fn pending_completion_container(icon: IconName) -> Div {
1220            h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
1221        }
1222
1223        let completion = match &self.active_edit_prediction {
1224            Some(prediction) => {
1225                if !self.has_visible_completions_menu() {
1226                    const RADIUS: Pixels = px(6.);
1227                    const BORDER_WIDTH: Pixels = px(1.);
1228                    let keybind_display = self.edit_prediction_keybind_display(
1229                        EditPredictionKeybindSurface::CursorPopoverCompact,
1230                        window,
1231                        cx,
1232                    );
1233
1234                    return Some(
1235                        h_flex()
1236                            .elevation_2(cx)
1237                            .border(BORDER_WIDTH)
1238                            .border_color(cx.theme().colors().border)
1239                            .when(keybind_display.missing_accept_keystroke, |el| {
1240                                el.border_color(cx.theme().status().error)
1241                            })
1242                            .rounded(RADIUS)
1243                            .rounded_tl(px(0.))
1244                            .overflow_hidden()
1245                            .child(div().px_1p5().child(match &prediction.completion {
1246                                EditPrediction::MoveWithin { target, snapshot } => {
1247                                    use text::ToPoint as _;
1248                                    if target.text_anchor_in(&snapshot).to_point(snapshot).row
1249                                        > cursor_point.row
1250                                    {
1251                                        Icon::new(icons.down)
1252                                    } else {
1253                                        Icon::new(icons.up)
1254                                    }
1255                                }
1256                                EditPrediction::MoveOutside { .. } => {
1257                                    // TODO [zeta2] custom icon for external jump?
1258                                    Icon::new(icons.base)
1259                                }
1260                                EditPrediction::Edit { .. } => Icon::new(icons.base),
1261                            }))
1262                            .child(
1263                                h_flex()
1264                                    .gap_1()
1265                                    .py_1()
1266                                    .px_2()
1267                                    .rounded_r(RADIUS - BORDER_WIDTH)
1268                                    .border_l_1()
1269                                    .border_color(cx.theme().colors().border)
1270                                    .bg(Self::edit_prediction_line_popover_bg_color(cx))
1271                                    .when(keybind_display.show_hold_label, |el| {
1272                                        el.child(
1273                                            Label::new("Hold")
1274                                                .size(LabelSize::Small)
1275                                                .when(
1276                                                    keybind_display.missing_accept_keystroke,
1277                                                    |el| el.strikethrough(),
1278                                                )
1279                                                .line_height_style(LineHeightStyle::UiLabel),
1280                                        )
1281                                    })
1282                                    .id("edit_prediction_cursor_popover_keybind")
1283                                    .when(keybind_display.missing_accept_keystroke, |el| {
1284                                        let status_colors = cx.theme().status();
1285
1286                                        el.bg(status_colors.error_background)
1287                                            .border_color(status_colors.error.opacity(0.6))
1288                                            .child(Icon::new(IconName::Info).color(Color::Error))
1289                                            .cursor_default()
1290                                            .hoverable_tooltip(move |_window, cx| {
1291                                                cx.new(|_| MissingEditPredictionKeybindingTooltip)
1292                                                    .into()
1293                                            })
1294                                    })
1295                                    .when_some(
1296                                        keybind_display.displayed_keystroke.as_ref(),
1297                                        |el, compact_keystroke| {
1298                                            el.child(self.render_edit_prediction_popover_keystroke(
1299                                                compact_keystroke,
1300                                                Color::Default,
1301                                                cx,
1302                                            ))
1303                                        },
1304                                    ),
1305                            )
1306                            .into_any(),
1307                    );
1308                }
1309
1310                self.render_edit_prediction_cursor_popover_preview(
1311                    prediction,
1312                    cursor_point,
1313                    style,
1314                    cx,
1315                )?
1316            }
1317
1318            None if is_refreshing => match &self.stale_edit_prediction_in_menu {
1319                Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
1320                    stale_completion,
1321                    cursor_point,
1322                    style,
1323                    cx,
1324                )?,
1325
1326                None => pending_completion_container(icons.base)
1327                    .child(Label::new("...").size(LabelSize::Small)),
1328            },
1329
1330            None => pending_completion_container(icons.base)
1331                .child(Label::new("...").size(LabelSize::Small)),
1332        };
1333
1334        let completion = if is_refreshing || self.active_edit_prediction.is_none() {
1335            completion
1336                .with_animation(
1337                    "loading-completion",
1338                    Animation::new(Duration::from_secs(2))
1339                        .repeat()
1340                        .with_easing(pulsating_between(0.4, 0.8)),
1341                    |label, delta| label.opacity(delta),
1342                )
1343                .into_any_element()
1344        } else {
1345            completion.into_any_element()
1346        };
1347
1348        let has_completion = self.active_edit_prediction.is_some();
1349        let keybind_display = self.edit_prediction_keybind_display(
1350            EditPredictionKeybindSurface::CursorPopoverExpanded,
1351            window,
1352            cx,
1353        );
1354
1355        Some(
1356            h_flex()
1357                .min_w(min_width)
1358                .max_w(max_width)
1359                .flex_1()
1360                .elevation_2(cx)
1361                .border_color(cx.theme().colors().border)
1362                .child(
1363                    div()
1364                        .flex_1()
1365                        .py_1()
1366                        .px_2()
1367                        .overflow_hidden()
1368                        .child(completion),
1369                )
1370                .when_some(
1371                    keybind_display.displayed_keystroke.as_ref(),
1372                    |el, keystroke| {
1373                        let key_color = if !has_completion {
1374                            Color::Muted
1375                        } else {
1376                            Color::Default
1377                        };
1378
1379                        if keybind_display.action == EditPredictionKeybindAction::Preview {
1380                            el.child(
1381                                h_flex()
1382                                    .h_full()
1383                                    .border_l_1()
1384                                    .rounded_r_lg()
1385                                    .border_color(cx.theme().colors().border)
1386                                    .bg(Self::edit_prediction_line_popover_bg_color(cx))
1387                                    .gap_1()
1388                                    .py_1()
1389                                    .px_2()
1390                                    .child(self.render_edit_prediction_popover_keystroke(
1391                                        keystroke, key_color, cx,
1392                                    ))
1393                                    .child(Label::new("Preview").into_any_element())
1394                                    .opacity(if has_completion { 1.0 } else { 0.4 }),
1395                            )
1396                        } else {
1397                            el.child(
1398                                h_flex()
1399                                    .h_full()
1400                                    .border_l_1()
1401                                    .rounded_r_lg()
1402                                    .border_color(cx.theme().colors().border)
1403                                    .bg(Self::edit_prediction_line_popover_bg_color(cx))
1404                                    .gap_1()
1405                                    .py_1()
1406                                    .px_2()
1407                                    .child(self.render_edit_prediction_popover_keystroke(
1408                                        keystroke, key_color, cx,
1409                                    ))
1410                                    .opacity(if has_completion { 1.0 } else { 0.4 }),
1411                            )
1412                        }
1413                    },
1414                )
1415                .into_any(),
1416        )
1417    }
1418
1419    fn accept_edit_prediction_keystroke(
1420        &self,
1421        granularity: EditPredictionGranularity,
1422        window: &mut Window,
1423        cx: &mut App,
1424    ) -> Option<gpui::KeybindingKeystroke> {
1425        let key_context = self.key_context_internal(true, window, cx);
1426
1427        let bindings =
1428            match granularity {
1429                EditPredictionGranularity::Word => window
1430                    .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
1431                EditPredictionGranularity::Line => window
1432                    .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
1433                EditPredictionGranularity::Full => {
1434                    window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1435                }
1436            };
1437
1438        bindings
1439            .into_iter()
1440            .rev()
1441            .find_map(|binding| match binding.keystrokes() {
1442                [keystroke, ..] => Some(keystroke.clone()),
1443                _ => None,
1444            })
1445    }
1446
1447    fn edit_prediction_preview_modifiers_held(
1448        &self,
1449        modifiers: &Modifiers,
1450        window: &mut Window,
1451        cx: &mut App,
1452    ) -> bool {
1453        let can_supersede_active_menu =
1454            self.context_menu.borrow().as_ref().is_none_or(|menu| {
1455                !menu.visible() || matches!(menu, CodeContextMenu::Completions(_))
1456            });
1457
1458        if !can_supersede_active_menu {
1459            return false;
1460        }
1461
1462        let key_context = self.key_context_internal(true, window, cx);
1463        let actions: [&dyn Action; 3] = [
1464            &AcceptEditPrediction,
1465            &AcceptNextWordEditPrediction,
1466            &AcceptNextLineEditPrediction,
1467        ];
1468
1469        actions.into_iter().any(|action| {
1470            window
1471                .bindings_for_action_in_context(action, key_context.clone())
1472                .into_iter()
1473                .rev()
1474                .any(|binding| {
1475                    binding.keystrokes().first().is_some_and(|keystroke| {
1476                        keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
1477                    })
1478                })
1479        })
1480    }
1481
1482    fn edit_prediction_cursor_popover_prefers_preview(
1483        &self,
1484        completion: &EditPredictionState,
1485        cx: &App,
1486    ) -> bool {
1487        let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
1488
1489        match &completion.completion {
1490            EditPrediction::Edit {
1491                edits, snapshot, ..
1492            } => {
1493                let mut start_row: Option<u32> = None;
1494                let mut end_row: Option<u32> = None;
1495
1496                for (range, text) in edits {
1497                    let Some((_, range)) =
1498                        multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
1499                    else {
1500                        continue;
1501                    };
1502                    let edit_start_row = range.start.to_point(snapshot).row;
1503                    let old_end_row = range.end.to_point(snapshot).row;
1504                    let inserted_newline_count = text
1505                        .as_ref()
1506                        .chars()
1507                        .filter(|character| *character == '\n')
1508                        .count() as u32;
1509                    let deleted_newline_count = old_end_row - edit_start_row;
1510                    let preview_end_row = edit_start_row + inserted_newline_count;
1511
1512                    start_row =
1513                        Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
1514                    end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
1515
1516                    if deleted_newline_count > 1 {
1517                        end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
1518                    }
1519                }
1520
1521                start_row
1522                    .zip(end_row)
1523                    .is_some_and(|(start_row, end_row)| end_row > start_row)
1524            }
1525            EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
1526        }
1527    }
1528
1529    fn edit_predictions_disabled_in_scope(
1530        &self,
1531        buffer: &Entity<Buffer>,
1532        buffer_position: language::Anchor,
1533        cx: &App,
1534    ) -> bool {
1535        let snapshot = buffer.read(cx).snapshot();
1536        let settings = snapshot.settings_at(buffer_position, cx);
1537
1538        let Some(scope) = snapshot.language_scope_at(buffer_position) else {
1539            return false;
1540        };
1541
1542        scope.override_name().is_some_and(|scope_name| {
1543            settings
1544                .edit_predictions_disabled_in
1545                .iter()
1546                .any(|s| s == scope_name)
1547        })
1548    }
1549
1550    fn edit_prediction_settings_at_position(
1551        &self,
1552        buffer: &Entity<Buffer>,
1553        buffer_position: language::Anchor,
1554        cx: &App,
1555    ) -> EditPredictionSettings {
1556        if !self.mode.is_full()
1557            || !self.show_edit_predictions_override.unwrap_or(true)
1558            || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
1559        {
1560            return EditPredictionSettings::Disabled;
1561        }
1562
1563        if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
1564            return EditPredictionSettings::Disabled;
1565        };
1566
1567        let by_provider = matches!(
1568            self.menu_edit_predictions_policy,
1569            MenuEditPredictionsPolicy::ByProvider
1570        );
1571
1572        let show_in_menu = by_provider
1573            && self
1574                .edit_prediction_provider
1575                .as_ref()
1576                .is_some_and(|provider| provider.provider.show_predictions_in_menu());
1577
1578        let file = buffer.read(cx).file();
1579        let preview_requires_modifier =
1580            all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
1581
1582        EditPredictionSettings::Enabled {
1583            show_in_menu,
1584            preview_requires_modifier,
1585        }
1586    }
1587
1588    fn should_show_edit_predictions(&self) -> bool {
1589        self.snippet_stack.is_empty() && self.edit_predictions_enabled()
1590    }
1591
1592    fn edit_predictions_enabled_in_buffer(
1593        &self,
1594        buffer: &Entity<Buffer>,
1595        buffer_position: language::Anchor,
1596        cx: &App,
1597    ) -> bool {
1598        maybe!({
1599            if self.read_only(cx) || self.leader_id.is_some() {
1600                return Some(false);
1601            }
1602            let provider = self.edit_prediction_provider()?;
1603            if !provider.is_enabled(buffer, buffer_position, cx) {
1604                return Some(false);
1605            }
1606            let buffer = buffer.read(cx);
1607            let Some(file) = buffer.file() else {
1608                return Some(true);
1609            };
1610            let settings = all_language_settings(Some(file), cx);
1611            Some(settings.edit_predictions_enabled_for_file(file, cx))
1612        })
1613        .unwrap_or(false)
1614    }
1615
1616    fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
1617        let Some(provider) = self.edit_prediction_provider() else {
1618            return;
1619        };
1620
1621        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
1622        let Some((position, _)) =
1623            buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
1624        else {
1625            return;
1626        };
1627        let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
1628            return;
1629        };
1630
1631        let extension = buffer
1632            .read(cx)
1633            .file()
1634            .and_then(|file| Some(file.path().extension()?.to_string()));
1635
1636        let event_type = match accepted {
1637            true => "Edit Prediction Accepted",
1638            false => "Edit Prediction Discarded",
1639        };
1640        telemetry::event!(
1641            event_type,
1642            provider = provider.name(),
1643            prediction_id = id,
1644            suggestion_accepted = accepted,
1645            file_extension = extension,
1646        );
1647    }
1648
1649    fn open_editor_at_anchor(
1650        snapshot: &language::BufferSnapshot,
1651        target: language::Anchor,
1652        workspace: &Entity<Workspace>,
1653        window: &mut Window,
1654        cx: &mut App,
1655    ) -> Task<Result<()>> {
1656        workspace.update(cx, |workspace, cx| {
1657            let path = snapshot.file().map(|file| file.full_path(cx));
1658            let Some(project_path) = path
1659                .as_ref()
1660                .and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
1661            else {
1662                return Task::ready(Err(anyhow::anyhow!(
1663                    "project path not found for edit prediction target {path:?}"
1664                )));
1665            };
1666            let target = text::ToPoint::to_point(&target, snapshot);
1667            let item = workspace.open_path(project_path, None, true, window, cx);
1668            window.spawn(cx, async move |cx| {
1669                let Some(editor) = item.await?.downcast::<Editor>() else {
1670                    return Ok(());
1671                };
1672                editor
1673                    .update_in(cx, |editor, window, cx| {
1674                        editor.go_to_singleton_buffer_point(target, window, cx);
1675                    })
1676                    .ok();
1677                anyhow::Ok(())
1678            })
1679        })
1680    }
1681
1682    const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
1683
1684    const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
1685
1686    fn render_edit_prediction_modifier_jump_popover(
1687        &mut self,
1688        text_bounds: &Bounds<Pixels>,
1689        content_origin: gpui::Point<Pixels>,
1690        visible_row_range: Range<DisplayRow>,
1691        line_layouts: &[LineWithInvisibles],
1692        line_height: Pixels,
1693        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
1694        newest_selection_head: Option<DisplayPoint>,
1695        target_display_point: DisplayPoint,
1696        window: &mut Window,
1697        cx: &mut App,
1698    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1699        let scrolled_content_origin =
1700            content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
1701
1702        const SCROLL_PADDING_Y: Pixels = px(12.);
1703
1704        if target_display_point.row() < visible_row_range.start {
1705            return self.render_edit_prediction_scroll_popover(
1706                &|_| SCROLL_PADDING_Y,
1707                IconName::ArrowUp,
1708                visible_row_range,
1709                line_layouts,
1710                newest_selection_head,
1711                scrolled_content_origin,
1712                window,
1713                cx,
1714            );
1715        } else if target_display_point.row() >= visible_row_range.end {
1716            return self.render_edit_prediction_scroll_popover(
1717                &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
1718                IconName::ArrowDown,
1719                visible_row_range,
1720                line_layouts,
1721                newest_selection_head,
1722                scrolled_content_origin,
1723                window,
1724                cx,
1725            );
1726        }
1727
1728        const POLE_WIDTH: Pixels = px(2.);
1729
1730        let line_layout =
1731            line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
1732        let target_column = target_display_point.column() as usize;
1733
1734        let target_x = line_layout.x_for_index(target_column);
1735        let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
1736            - scroll_pixel_position.y;
1737
1738        let flag_on_right = target_x < text_bounds.size.width / 2.;
1739
1740        let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
1741        border_color.l += 0.001;
1742
1743        let mut element = v_flex()
1744            .items_end()
1745            .when(flag_on_right, |el| el.items_start())
1746            .child(if flag_on_right {
1747                self.render_edit_prediction_line_popover("Jump", None, window, cx)
1748                    .rounded_bl(px(0.))
1749                    .rounded_tl(px(0.))
1750                    .border_l_2()
1751                    .border_color(border_color)
1752            } else {
1753                self.render_edit_prediction_line_popover("Jump", None, window, cx)
1754                    .rounded_br(px(0.))
1755                    .rounded_tr(px(0.))
1756                    .border_r_2()
1757                    .border_color(border_color)
1758            })
1759            .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
1760            .into_any();
1761
1762        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1763
1764        let mut origin = scrolled_content_origin + point(target_x, target_y.into())
1765            - point(
1766                if flag_on_right {
1767                    POLE_WIDTH
1768                } else {
1769                    size.width - POLE_WIDTH
1770                },
1771                size.height - line_height,
1772            );
1773
1774        origin.x = origin.x.max(content_origin.x);
1775
1776        element.prepaint_at(origin, window, cx);
1777
1778        Some((element, origin))
1779    }
1780
1781    fn render_edit_prediction_scroll_popover(
1782        &mut self,
1783        to_y: &dyn Fn(Size<Pixels>) -> Pixels,
1784        scroll_icon: IconName,
1785        visible_row_range: Range<DisplayRow>,
1786        line_layouts: &[LineWithInvisibles],
1787        newest_selection_head: Option<DisplayPoint>,
1788        scrolled_content_origin: gpui::Point<Pixels>,
1789        window: &mut Window,
1790        cx: &mut App,
1791    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1792        let mut element = self
1793            .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
1794            .into_any();
1795
1796        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1797
1798        let cursor = newest_selection_head?;
1799        let cursor_row_layout =
1800            line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
1801        let cursor_column = cursor.column() as usize;
1802
1803        let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
1804
1805        let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
1806
1807        element.prepaint_at(origin, window, cx);
1808        Some((element, origin))
1809    }
1810
1811    fn render_edit_prediction_eager_jump_popover(
1812        &mut self,
1813        text_bounds: &Bounds<Pixels>,
1814        content_origin: gpui::Point<Pixels>,
1815        editor_snapshot: &EditorSnapshot,
1816        visible_row_range: Range<DisplayRow>,
1817        scroll_top: ScrollOffset,
1818        scroll_bottom: ScrollOffset,
1819        line_height: Pixels,
1820        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
1821        target_display_point: DisplayPoint,
1822        editor_width: Pixels,
1823        window: &mut Window,
1824        cx: &mut App,
1825    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1826        if target_display_point.row().as_f64() < scroll_top {
1827            let mut element = self
1828                .render_edit_prediction_line_popover(
1829                    "Jump to Edit",
1830                    Some(IconName::ArrowUp),
1831                    window,
1832                    cx,
1833                )
1834                .into_any();
1835
1836            let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1837            let offset = point(
1838                (text_bounds.size.width - size.width) / 2.,
1839                Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
1840            );
1841
1842            let origin = text_bounds.origin + offset;
1843            element.prepaint_at(origin, window, cx);
1844            Some((element, origin))
1845        } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
1846            let mut element = self
1847                .render_edit_prediction_line_popover(
1848                    "Jump to Edit",
1849                    Some(IconName::ArrowDown),
1850                    window,
1851                    cx,
1852                )
1853                .into_any();
1854
1855            let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1856            let offset = point(
1857                (text_bounds.size.width - size.width) / 2.,
1858                text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
1859            );
1860
1861            let origin = text_bounds.origin + offset;
1862            element.prepaint_at(origin, window, cx);
1863            Some((element, origin))
1864        } else {
1865            self.render_edit_prediction_end_of_line_popover(
1866                "Jump to Edit",
1867                editor_snapshot,
1868                visible_row_range,
1869                target_display_point,
1870                line_height,
1871                scroll_pixel_position,
1872                content_origin,
1873                editor_width,
1874                window,
1875                cx,
1876            )
1877        }
1878    }
1879
1880    fn render_edit_prediction_end_of_line_popover(
1881        self: &mut Editor,
1882        label: &'static str,
1883        editor_snapshot: &EditorSnapshot,
1884        visible_row_range: Range<DisplayRow>,
1885        target_display_point: DisplayPoint,
1886        line_height: Pixels,
1887        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
1888        content_origin: gpui::Point<Pixels>,
1889        editor_width: Pixels,
1890        window: &mut Window,
1891        cx: &mut App,
1892    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1893        let target_line_end = DisplayPoint::new(
1894            target_display_point.row(),
1895            editor_snapshot.line_len(target_display_point.row()),
1896        );
1897
1898        let mut element = self
1899            .render_edit_prediction_line_popover(label, None, window, cx)
1900            .into_any();
1901
1902        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1903
1904        let line_origin =
1905            self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
1906
1907        let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
1908        let mut origin = start_point
1909            + line_origin
1910            + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
1911        origin.x = origin.x.max(content_origin.x);
1912
1913        let max_x = content_origin.x + editor_width - size.width;
1914
1915        if origin.x > max_x {
1916            let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
1917
1918            let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
1919                origin.y += offset;
1920                IconName::ArrowUp
1921            } else {
1922                origin.y -= offset;
1923                IconName::ArrowDown
1924            };
1925
1926            element = self
1927                .render_edit_prediction_line_popover(label, Some(icon), window, cx)
1928                .into_any();
1929
1930            let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
1931
1932            origin.x = content_origin.x + editor_width - size.width - px(2.);
1933        }
1934
1935        element.prepaint_at(origin, window, cx);
1936        Some((element, origin))
1937    }
1938
1939    fn render_edit_prediction_diff_popover(
1940        self: &Editor,
1941        text_bounds: &Bounds<Pixels>,
1942        content_origin: gpui::Point<Pixels>,
1943        right_margin: Pixels,
1944        editor_snapshot: &EditorSnapshot,
1945        visible_row_range: Range<DisplayRow>,
1946        line_layouts: &[LineWithInvisibles],
1947        line_height: Pixels,
1948        scroll_position: gpui::Point<ScrollOffset>,
1949        scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
1950        newest_selection_head: Option<DisplayPoint>,
1951        editor_width: Pixels,
1952        style: &EditorStyle,
1953        edits: &Vec<(Range<Anchor>, Arc<str>)>,
1954        edit_preview: &Option<language::EditPreview>,
1955        snapshot: &language::BufferSnapshot,
1956        window: &mut Window,
1957        cx: &mut App,
1958    ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
1959        let Some((first_edit_range, _)) = edits.first() else {
1960            return None;
1961        };
1962        let Some((last_edit_range, _)) = edits.last() else {
1963            return None;
1964        };
1965
1966        let edit_start = first_edit_range.start.to_display_point(editor_snapshot);
1967        let edit_end = last_edit_range.end.to_display_point(editor_snapshot);
1968
1969        let is_visible = visible_row_range.contains(&edit_start.row())
1970            || visible_row_range.contains(&edit_end.row());
1971        if !is_visible {
1972            return None;
1973        }
1974
1975        let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
1976            edit_prediction_edit_text(
1977                snapshot,
1978                edits,
1979                edit_preview,
1980                false,
1981                editor_snapshot.buffer_snapshot(),
1982                cx,
1983            )
1984        } else {
1985            // Fallback for providers without edit_preview
1986            edit_prediction_fallback_text(edits, cx)
1987        };
1988
1989        let styled_text = highlighted_edits.to_styled_text(&style.text);
1990        let line_count = highlighted_edits.text.lines().count();
1991
1992        const BORDER_WIDTH: Pixels = px(1.);
1993
1994        let keybind = self.render_edit_prediction_keybind(window, cx);
1995        let has_keybind = keybind.is_some();
1996
1997        let mut element = h_flex()
1998            .items_start()
1999            .debug_selector(|| "edit_prediction_diff_popover".into())
2000            .child(
2001                h_flex()
2002                    .bg(cx.theme().colors().editor_background)
2003                    .border(BORDER_WIDTH)
2004                    .shadow_xs()
2005                    .border_color(cx.theme().colors().border)
2006                    .rounded_l_lg()
2007                    .when(line_count > 1, |el| el.rounded_br_lg())
2008                    .pr_1()
2009                    .child(styled_text),
2010            )
2011            .child(
2012                h_flex()
2013                    .h(line_height + BORDER_WIDTH * 2.)
2014                    .px_1p5()
2015                    .gap_1()
2016                    // Workaround: For some reason, there's a gap if we don't do this
2017                    .ml(-BORDER_WIDTH)
2018                    .shadow(vec![
2019                        gpui::BoxShadow::new(px(1.), px(1.), gpui::black().opacity(0.05))
2020                            .blur_radius(px(2.)),
2021                    ])
2022                    .bg(Editor::edit_prediction_line_popover_bg_color(cx))
2023                    .border(BORDER_WIDTH)
2024                    .border_color(cx.theme().colors().border)
2025                    .rounded_r_lg()
2026                    .id("edit_prediction_diff_popover_keybind")
2027                    .when(!has_keybind, |el| {
2028                        let status_colors = cx.theme().status();
2029
2030                        el.bg(status_colors.error_background)
2031                            .border_color(status_colors.error.opacity(0.6))
2032                            .child(Icon::new(IconName::Info).color(Color::Error))
2033                            .cursor_default()
2034                            .hoverable_tooltip(move |_window, cx| {
2035                                cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
2036                            })
2037                    })
2038                    .children(keybind),
2039            )
2040            .into_any();
2041
2042        let longest_row =
2043            editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
2044        let longest_line_width = if visible_row_range.contains(&longest_row) {
2045            line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
2046        } else {
2047            layout_line(
2048                longest_row,
2049                editor_snapshot,
2050                style,
2051                editor_width,
2052                |_| false,
2053                window,
2054                cx,
2055            )
2056            .width
2057        };
2058
2059        let viewport_bounds =
2060            Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
2061                right: -right_margin,
2062                ..Default::default()
2063            });
2064        let popover_right_bound = cmp::min(text_bounds.right(), viewport_bounds.right());
2065
2066        let x_after_longest = Pixels::from(
2067            ScrollPixelOffset::from(
2068                text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
2069            ) - scroll_pixel_position.x,
2070        );
2071
2072        let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
2073
2074        let can_position_to_the_right = x_after_longest < text_bounds.right()
2075            && element_bounds.width <= popover_right_bound - text_bounds.left();
2076
2077        let mut origin = if can_position_to_the_right {
2078            point(
2079                x_after_longest.min(popover_right_bound - element_bounds.width),
2080                text_bounds.origin.y
2081                    + Pixels::from(
2082                        edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
2083                            - scroll_pixel_position.y,
2084                    ),
2085            )
2086        } else {
2087            let cursor_row = newest_selection_head.map(|head| head.row());
2088            let above_edit = edit_start
2089                .row()
2090                .0
2091                .checked_sub(line_count as u32)
2092                .map(DisplayRow);
2093            let below_edit = Some(edit_end.row() + 1);
2094            let above_cursor =
2095                cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
2096            let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
2097
2098            // Place the edit popover adjacent to the edit if there is a location
2099            // available that is onscreen and does not obscure the cursor. Otherwise,
2100            // place it adjacent to the cursor.
2101            let row_target = [above_edit, below_edit, above_cursor, below_cursor]
2102                .into_iter()
2103                .flatten()
2104                .find(|&start_row| {
2105                    let end_row = start_row + line_count as u32;
2106                    visible_row_range.contains(&start_row)
2107                        && visible_row_range.contains(&end_row)
2108                        && cursor_row
2109                            .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
2110                })?;
2111
2112            content_origin
2113                + point(
2114                    Pixels::from(-scroll_pixel_position.x),
2115                    Pixels::from(
2116                        (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
2117                    ),
2118                )
2119        };
2120
2121        origin.x -= BORDER_WIDTH;
2122
2123        window.with_content_mask(
2124            Some(gpui::ContentMask {
2125                bounds: *text_bounds,
2126            }),
2127            |window| {
2128                window.defer_draw(element, origin, 1, Some(window.content_mask()));
2129            },
2130        );
2131
2132        // Do not return an element, since it will already be drawn due to defer_draw.
2133        None
2134    }
2135
2136    fn render_edit_prediction_inline_keystroke(
2137        &self,
2138        keystroke: &gpui::KeybindingKeystroke,
2139        modifiers_color: Color,
2140        cx: &App,
2141    ) -> AnyElement {
2142        let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
2143
2144        h_flex()
2145            .px_0p5()
2146            .when(is_platform_style_mac, |parent| parent.gap_0p5())
2147            .font(
2148                theme_settings::ThemeSettings::get_global(cx)
2149                    .buffer_font
2150                    .clone(),
2151            )
2152            .text_size(TextSize::XSmall.rems(cx))
2153            .child(h_flex().children(ui::render_modifiers(
2154                keystroke.modifiers(),
2155                PlatformStyle::platform(),
2156                Some(modifiers_color),
2157                Some(IconSize::XSmall.rems().into()),
2158                true,
2159            )))
2160            .when(is_platform_style_mac, |parent| {
2161                parent.child(keystroke.key().to_string())
2162            })
2163            .when(!is_platform_style_mac, |parent| {
2164                parent.child(
2165                    Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
2166                        .size(Some(IconSize::XSmall.rems().into())),
2167                )
2168            })
2169            .into_any()
2170    }
2171
2172    fn render_edit_prediction_popover_keystroke(
2173        &self,
2174        keystroke: &gpui::KeybindingKeystroke,
2175        color: Color,
2176        cx: &App,
2177    ) -> AnyElement {
2178        let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
2179
2180        if keystroke.modifiers().modified() {
2181            h_flex()
2182                .font(
2183                    theme_settings::ThemeSettings::get_global(cx)
2184                        .buffer_font
2185                        .clone(),
2186                )
2187                .when(is_platform_style_mac, |parent| parent.gap_1())
2188                .child(h_flex().children(ui::render_modifiers(
2189                    keystroke.modifiers(),
2190                    PlatformStyle::platform(),
2191                    Some(color),
2192                    None,
2193                    false,
2194                )))
2195                .into_any()
2196        } else {
2197            Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
2198                .size(Some(IconSize::XSmall.rems().into()))
2199                .into_any_element()
2200        }
2201    }
2202
2203    fn render_edit_prediction_keybind(
2204        &self,
2205        window: &mut Window,
2206        cx: &mut App,
2207    ) -> Option<AnyElement> {
2208        let keybind_display =
2209            self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
2210        let keystroke = keybind_display.displayed_keystroke.as_ref()?;
2211
2212        let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
2213            Color::Accent
2214        } else {
2215            Color::Muted
2216        };
2217
2218        Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
2219    }
2220
2221    fn render_edit_prediction_line_popover(
2222        &self,
2223        label: impl Into<SharedString>,
2224        icon: Option<IconName>,
2225        window: &mut Window,
2226        cx: &mut App,
2227    ) -> Stateful<Div> {
2228        let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
2229
2230        let keybind = self.render_edit_prediction_keybind(window, cx);
2231        let has_keybind = keybind.is_some();
2232        let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
2233
2234        h_flex()
2235            .id("ep-line-popover")
2236            .py_0p5()
2237            .pl_1()
2238            .pr(padding_right)
2239            .gap_1()
2240            .rounded_md()
2241            .border_1()
2242            .bg(Self::edit_prediction_line_popover_bg_color(cx))
2243            .border_color(Self::edit_prediction_callout_popover_border_color(cx))
2244            .shadow_xs()
2245            .when(!has_keybind, |el| {
2246                let status_colors = cx.theme().status();
2247
2248                el.bg(status_colors.error_background)
2249                    .border_color(status_colors.error.opacity(0.6))
2250                    .pl_2()
2251                    .child(Icon::new(icons.error).color(Color::Error))
2252                    .cursor_default()
2253                    .hoverable_tooltip(move |_window, cx| {
2254                        cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
2255                    })
2256            })
2257            .children(keybind)
2258            .child(
2259                Label::new(label)
2260                    .size(LabelSize::Small)
2261                    .when(!has_keybind, |el| {
2262                        el.color(cx.theme().status().error.into()).strikethrough()
2263                    }),
2264            )
2265            .when(!has_keybind, |el| {
2266                el.child(
2267                    h_flex().ml_1().child(
2268                        Icon::new(IconName::Info)
2269                            .size(IconSize::Small)
2270                            .color(cx.theme().status().error.into()),
2271                    ),
2272                )
2273            })
2274            .when_some(icon, |element, icon| {
2275                element.child(
2276                    div()
2277                        .mt(px(1.5))
2278                        .child(Icon::new(icon).size(IconSize::Small)),
2279                )
2280            })
2281    }
2282
2283    fn render_edit_prediction_jump_outside_popover(
2284        &self,
2285        snapshot: &BufferSnapshot,
2286        window: &mut Window,
2287        cx: &mut App,
2288    ) -> Stateful<Div> {
2289        let keybind = self.render_edit_prediction_keybind(window, cx);
2290        let has_keybind = keybind.is_some();
2291        let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
2292
2293        let file_name = snapshot
2294            .file()
2295            .map(|file| SharedString::new(file.file_name(cx)))
2296            .unwrap_or(SharedString::new_static(MultiBuffer::DEFAULT_TITLE));
2297
2298        h_flex()
2299            .id("ep-jump-outside-popover")
2300            .py_1()
2301            .px_2()
2302            .gap_1()
2303            .rounded_md()
2304            .border_1()
2305            .bg(Self::edit_prediction_line_popover_bg_color(cx))
2306            .border_color(Self::edit_prediction_callout_popover_border_color(cx))
2307            .shadow_xs()
2308            .when(!has_keybind, |el| {
2309                let status_colors = cx.theme().status();
2310
2311                el.bg(status_colors.error_background)
2312                    .border_color(status_colors.error.opacity(0.6))
2313                    .pl_2()
2314                    .child(Icon::new(icons.error).color(Color::Error))
2315                    .cursor_default()
2316                    .hoverable_tooltip(move |_window, cx| {
2317                        cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
2318                    })
2319            })
2320            .children(keybind)
2321            .child(
2322                Label::new(file_name)
2323                    .size(LabelSize::Small)
2324                    .buffer_font(cx)
2325                    .when(!has_keybind, |el| {
2326                        el.color(cx.theme().status().error.into()).strikethrough()
2327                    }),
2328            )
2329            .when(!has_keybind, |el| {
2330                el.child(
2331                    h_flex().ml_1().child(
2332                        Icon::new(IconName::Info)
2333                            .size(IconSize::Small)
2334                            .color(cx.theme().status().error.into()),
2335                    ),
2336                )
2337            })
2338            .child(
2339                div()
2340                    .mt(px(1.5))
2341                    .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
2342            )
2343    }
2344
2345    fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
2346        let accent_color = cx.theme().colors().text_accent;
2347        let editor_bg_color = cx.theme().colors().editor_background;
2348        editor_bg_color.blend(accent_color.opacity(0.1))
2349    }
2350
2351    fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
2352        let accent_color = cx.theme().colors().text_accent;
2353        let editor_bg_color = cx.theme().colors().editor_background;
2354        editor_bg_color.blend(accent_color.opacity(0.6))
2355    }
2356
2357    fn get_prediction_provider_icons(
2358        provider: &Option<RegisteredEditPredictionDelegate>,
2359        cx: &App,
2360    ) -> edit_prediction_types::EditPredictionIconSet {
2361        match provider {
2362            Some(provider) => provider.provider.icons(cx),
2363            None => edit_prediction_types::EditPredictionIconSet::new(IconName::OmegaPredict),
2364        }
2365    }
2366
2367    fn render_edit_prediction_cursor_popover_preview(
2368        &self,
2369        completion: &EditPredictionState,
2370        cursor_point: Point,
2371        style: &EditorStyle,
2372        cx: &mut Context<Editor>,
2373    ) -> Option<Div> {
2374        use text::ToPoint as _;
2375
2376        fn render_relative_row_jump(
2377            prefix: impl Into<String>,
2378            current_row: u32,
2379            target_row: u32,
2380        ) -> Div {
2381            let (row_diff, arrow) = if target_row < current_row {
2382                (current_row - target_row, IconName::ArrowUp)
2383            } else {
2384                (target_row - current_row, IconName::ArrowDown)
2385            };
2386
2387            h_flex()
2388                .child(
2389                    Label::new(format!("{}{}", prefix.into(), row_diff))
2390                        .color(Color::Muted)
2391                        .size(LabelSize::Small),
2392                )
2393                .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
2394        }
2395
2396        let supports_jump = self
2397            .edit_prediction_provider
2398            .as_ref()
2399            .map(|provider| provider.provider.supports_jump_to_edit())
2400            .unwrap_or(true);
2401
2402        let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
2403
2404        match &completion.completion {
2405            EditPrediction::MoveWithin {
2406                target, snapshot, ..
2407            } => {
2408                if !supports_jump {
2409                    return None;
2410                }
2411                let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?;
2412
2413                Some(
2414                    h_flex()
2415                        .px_2()
2416                        .gap_2()
2417                        .flex_1()
2418                        .child(if target.to_point(snapshot).row > cursor_point.row {
2419                            Icon::new(icons.down)
2420                        } else {
2421                            Icon::new(icons.up)
2422                        })
2423                        .child(Label::new("Jump to Edit")),
2424                )
2425            }
2426            EditPrediction::MoveOutside { snapshot, .. } => {
2427                let file_name = snapshot
2428                    .file()
2429                    .map(|file| file.file_name(cx))
2430                    .unwrap_or(MultiBuffer::DEFAULT_TITLE);
2431                Some(
2432                    h_flex()
2433                        .px_2()
2434                        .gap_2()
2435                        .flex_1()
2436                        .child(Icon::new(icons.base))
2437                        .child(Label::new(format!("Jump to {file_name}"))),
2438                )
2439            }
2440            EditPrediction::Edit {
2441                edits,
2442                edit_preview,
2443                snapshot,
2444                ..
2445            } => {
2446                let first_edit_row = self
2447                    .display_snapshot(cx)
2448                    .anchor_to_buffer_anchor(edits.first()?.0.start)?
2449                    .0
2450                    .to_point(snapshot)
2451                    .row;
2452
2453                let (highlighted_edits, has_more_lines) =
2454                    if let Some(edit_preview) = edit_preview.as_ref() {
2455                        edit_prediction_edit_text(
2456                            snapshot,
2457                            edits,
2458                            edit_preview,
2459                            true,
2460                            &self.display_snapshot(cx),
2461                            cx,
2462                        )
2463                        .first_line_preview()
2464                    } else {
2465                        edit_prediction_fallback_text(edits, cx).first_line_preview()
2466                    };
2467
2468                let styled_text = gpui::StyledText::new(highlighted_edits.text)
2469                    .with_default_highlights(&style.text, highlighted_edits.highlights);
2470
2471                let preview = h_flex()
2472                    .gap_1()
2473                    .min_w_16()
2474                    .child(styled_text)
2475                    .when(has_more_lines, |parent| parent.child("…"));
2476
2477                let left = if supports_jump && first_edit_row != cursor_point.row {
2478                    render_relative_row_jump("", cursor_point.row, first_edit_row)
2479                        .into_any_element()
2480                } else {
2481                    Icon::new(icons.base).into_any_element()
2482                };
2483
2484                Some(
2485                    h_flex()
2486                        .h_full()
2487                        .flex_1()
2488                        .gap_2()
2489                        .pr_1()
2490                        .overflow_x_hidden()
2491                        .font(
2492                            theme_settings::ThemeSettings::get_global(cx)
2493                                .buffer_font
2494                                .clone(),
2495                        )
2496                        .child(left)
2497                        .child(preview),
2498                )
2499            }
2500        }
2501    }
2502}
2503
2504#[cfg(test)]
2505impl Editor {
2506    pub(super) fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2507        self.menu_edit_predictions_policy = value;
2508    }
2509}
2510
2511struct MissingEditPredictionKeybindingTooltip;
2512
2513impl Render for MissingEditPredictionKeybindingTooltip {
2514    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2515        ui::tooltip_container(cx, |container, cx| {
2516            container
2517                .flex_shrink_0()
2518                .max_w_80()
2519                .min_h(rems_from_px(124.))
2520                .justify_between()
2521                .child(
2522                    v_flex()
2523                        .flex_1()
2524                        .text_ui_sm(cx)
2525                        .child(Label::new("Conflict with Accept Keybinding"))
2526                        .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
2527                )
2528                .child(
2529                    h_flex()
2530                        .pb_1()
2531                        .gap_1()
2532                        .items_end()
2533                        .w_full()
2534                        .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
2535                            window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
2536                        }))
2537                        .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
2538                            cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
2539                        })),
2540                )
2541        })
2542    }
2543}
2544
2545fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
2546    // Fallback for providers that don't provide edit_preview (like Copilot)
2547    // Just show the raw edit text with basic styling
2548    let mut text = String::new();
2549    let mut highlights = Vec::new();
2550
2551    let insertion_highlight_style = HighlightStyle {
2552        color: Some(cx.theme().colors().text),
2553        ..Default::default()
2554    };
2555
2556    for (_, edit_text) in edits {
2557        let start_offset = text.len();
2558        text.push_str(edit_text);
2559        let end_offset = text.len();
2560
2561        if start_offset < end_offset {
2562            highlights.push((start_offset..end_offset, insertion_highlight_style));
2563        }
2564    }
2565
2566    HighlightedText {
2567        text: text.into(),
2568        highlights,
2569    }
2570}
2571
2572fn all_edits_insertions_or_deletions(
2573    edits: &Vec<(Range<Anchor>, Arc<str>)>,
2574    snapshot: &MultiBufferSnapshot,
2575) -> bool {
2576    let mut all_insertions = true;
2577    let mut all_deletions = true;
2578
2579    for (range, new_text) in edits.iter() {
2580        let range_is_empty = range.to_offset(snapshot).is_empty();
2581        let text_is_empty = new_text.is_empty();
2582
2583        if range_is_empty != text_is_empty {
2584            if range_is_empty {
2585                all_deletions = false;
2586            } else {
2587                all_insertions = false;
2588            }
2589        } else {
2590            return false;
2591        }
2592
2593        if !all_insertions && !all_deletions {
2594            return false;
2595        }
2596    }
2597    all_insertions || all_deletions
2598}
2599
Served at tenant.openagents/omega Member data and write actions are omitted.