Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:00:44.394Z 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

rate_prediction_modal.rs

1514 lines · 63.5 KB · rust
1use buffer_diff::BufferDiff;
2use cloud_llm_client::PredictEditsRequestTrigger;
3use edit_prediction::{
4    EditPrediction, EditPredictionInputs, EditPredictionRating, EditPredictionStore,
5};
6use editor::{Editor, Inlay, MultiBuffer};
7use feature_flags::{FeatureFlag, PresenceFlag, register_feature_flag};
8use gpui::{
9    App, BorderStyle, DismissEvent, EdgesRefinement, Entity, EventEmitter, FocusHandle, Focusable,
10    Length, StyleRefinement, Task, TextStyleRefinement, Window, actions, prelude::*,
11};
12use language::{
13    Bias, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, Point, ToOffset, ToPoint,
14    language_settings::{self, InlayHintKind},
15};
16use markdown::{Markdown, MarkdownStyle};
17use project::{
18    Completion, CompletionDisplayOptions, CompletionResponse, CompletionSource, InlayHint,
19    InlayHintLabel, InlayId, ResolveState,
20};
21use settings::Settings as _;
22use std::rc::Rc;
23use std::{fmt::Write, ops::Range, path::Path, sync::Arc};
24use theme_settings::ThemeSettings;
25use ui::{
26    ContextMenu, DropdownMenu, KeyBinding, List, ListItem, ListItemSpacing, PopoverMenuHandle,
27    Tooltip, prelude::*,
28};
29use workspace::{ModalView, Workspace};
30use zeta_prompt::{ContextSource, FilePosition, RelatedExcerpt, RelatedFile, Zeta3PromptInput};
31
32actions!(
33    zeta,
34    [
35        /// Rates the active completion with a thumbs up.
36        ThumbsUpActivePrediction,
37        /// Rates the active completion with a thumbs down.
38        ThumbsDownActivePrediction,
39        /// Navigates to the next edit in the completion history.
40        NextEdit,
41        /// Navigates to the previous edit in the completion history.
42        PreviousEdit,
43        /// Focuses on the completions list.
44        FocusPredictions,
45        /// Previews the selected completion.
46        PreviewPrediction,
47    ]
48);
49
50pub struct PredictEditsRatePredictionsFeatureFlag;
51
52impl FeatureFlag for PredictEditsRatePredictionsFeatureFlag {
53    const NAME: &'static str = "predict-edits-rate-completions";
54    type Value = PresenceFlag;
55}
56register_feature_flag!(PredictEditsRatePredictionsFeatureFlag);
57
58pub struct RatePredictionsModal {
59    ep_store: Entity<EditPredictionStore>,
60    language_registry: Arc<LanguageRegistry>,
61    active_prediction: Option<ActivePrediction>,
62    selected_index: usize,
63    diff_editor: Entity<Editor>,
64    focus_handle: FocusHandle,
65    _subscription: gpui::Subscription,
66    current_view: RatePredictionView,
67    failure_mode_menu_handle: PopoverMenuHandle<ContextMenu>,
68}
69
70struct ActivePrediction {
71    prediction: EditPrediction,
72    feedback_editor: Entity<Editor>,
73    expected_buffer: Entity<Buffer>,
74    expected_editor: Entity<Editor>,
75    _expected_buffer_subscription: gpui::Subscription,
76    formatted_inputs: Entity<Markdown>,
77    _predicted_diff_task: Task<()>,
78    expected_diff_task: Task<()>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
82enum RatePredictionView {
83    SuggestedEdits,
84    RawInput,
85}
86
87impl RatePredictionView {
88    pub fn name(&self) -> &'static str {
89        match self {
90            Self::SuggestedEdits => "Suggested Edits",
91            Self::RawInput => "Recorded Events & Input",
92        }
93    }
94}
95
96impl RatePredictionsModal {
97    pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
98        if let Some(ep_store) = EditPredictionStore::try_global(cx) {
99            let language_registry = workspace.app_state().languages.clone();
100            workspace.toggle_modal(window, cx, |window, cx| {
101                RatePredictionsModal::new(ep_store, language_registry, window, cx)
102            });
103
104            telemetry::event!("Rate Prediction Modal Open", source = "Edit Prediction");
105        }
106    }
107
108    pub fn new(
109        ep_store: Entity<EditPredictionStore>,
110        language_registry: Arc<LanguageRegistry>,
111        window: &mut Window,
112        cx: &mut Context<Self>,
113    ) -> Self {
114        let subscription = cx.observe(&ep_store, |_, _, cx| cx.notify());
115
116        Self {
117            ep_store,
118            language_registry,
119            selected_index: 0,
120            focus_handle: cx.focus_handle(),
121            active_prediction: None,
122            _subscription: subscription,
123            diff_editor: cx.new(|cx| {
124                let multibuffer = cx.new(|_| MultiBuffer::new(language::Capability::ReadOnly));
125                let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
126                editor.disable_inline_diagnostics();
127                editor.set_expand_all_diff_hunks(cx);
128                editor.set_show_git_diff_gutter(false, cx);
129                editor
130            }),
131            current_view: RatePredictionView::SuggestedEdits,
132            failure_mode_menu_handle: PopoverMenuHandle::default(),
133        }
134    }
135
136    fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
137        cx.emit(DismissEvent);
138    }
139
140    fn select_next(&mut self, _: &menu::SelectNext, _: &mut Window, cx: &mut Context<Self>) {
141        self.selected_index += 1;
142        self.selected_index = usize::min(
143            self.selected_index,
144            self.ep_store.read(cx).rateable_predictions().count(),
145        );
146        cx.notify();
147    }
148
149    fn select_previous(
150        &mut self,
151        _: &menu::SelectPrevious,
152        _: &mut Window,
153        cx: &mut Context<Self>,
154    ) {
155        self.selected_index = self.selected_index.saturating_sub(1);
156        cx.notify();
157    }
158
159    fn select_next_edit(&mut self, _: &NextEdit, _: &mut Window, cx: &mut Context<Self>) {
160        let next_index = self
161            .ep_store
162            .read(cx)
163            .rateable_predictions()
164            .skip(self.selected_index)
165            .enumerate()
166            .skip(1) // Skip straight to the next item
167            .find(|(_, completion)| !completion.edits.is_empty())
168            .map(|(ix, _)| ix + self.selected_index);
169
170        if let Some(next_index) = next_index {
171            self.selected_index = next_index;
172            cx.notify();
173        }
174    }
175
176    fn select_prev_edit(&mut self, _: &PreviousEdit, _: &mut Window, cx: &mut Context<Self>) {
177        let ep_store = self.ep_store.read(cx);
178        let completions_len = ep_store.rateable_predictions_count();
179
180        let prev_index = self
181            .ep_store
182            .read(cx)
183            .rateable_predictions()
184            .rev()
185            .skip((completions_len - 1) - self.selected_index)
186            .enumerate()
187            .skip(1) // Skip straight to the previous item
188            .find(|(_, completion)| !completion.edits.is_empty())
189            .map(|(ix, _)| self.selected_index - ix);
190
191        if let Some(prev_index) = prev_index {
192            self.selected_index = prev_index;
193            cx.notify();
194        }
195        cx.notify();
196    }
197
198    fn select_first(&mut self, _: &menu::SelectFirst, _: &mut Window, cx: &mut Context<Self>) {
199        self.selected_index = 0;
200        cx.notify();
201    }
202
203    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
204        self.selected_index = self.ep_store.read(cx).rateable_predictions_count() - 1;
205        cx.notify();
206    }
207
208    pub fn thumbs_up_active(
209        &mut self,
210        _: &ThumbsUpActivePrediction,
211        window: &mut Window,
212        cx: &mut Context<Self>,
213    ) {
214        self.ep_store.update(cx, |ep_store, cx| {
215            if let Some(active) = &self.active_prediction {
216                ep_store.rate_prediction(
217                    &active.prediction,
218                    EditPredictionRating::Positive,
219                    active.feedback_editor.read(cx).text(cx),
220                    self.expected_patch_for_active(cx),
221                    cx,
222                );
223            }
224        });
225
226        let current_completion = self
227            .active_prediction
228            .as_ref()
229            .map(|completion| completion.prediction.clone());
230        self.select_completion(current_completion, false, window, cx);
231        self.select_next_edit(&Default::default(), window, cx);
232        self.confirm(&Default::default(), window, cx);
233
234        cx.notify();
235    }
236
237    pub fn thumbs_down_active(
238        &mut self,
239        _: &ThumbsDownActivePrediction,
240        window: &mut Window,
241        cx: &mut Context<Self>,
242    ) {
243        if let Some(active) = &self.active_prediction {
244            if active.feedback_editor.read(cx).text(cx).is_empty() {
245                return;
246            }
247
248            self.ep_store.update(cx, |ep_store, cx| {
249                ep_store.rate_prediction(
250                    &active.prediction,
251                    EditPredictionRating::Negative,
252                    active.feedback_editor.read(cx).text(cx),
253                    self.expected_patch_for_active(cx),
254                    cx,
255                );
256            });
257        }
258
259        let current_completion = self
260            .active_prediction
261            .as_ref()
262            .map(|completion| completion.prediction.clone());
263        self.select_completion(current_completion, false, window, cx);
264        self.select_next_edit(&Default::default(), window, cx);
265        self.confirm(&Default::default(), window, cx);
266
267        cx.notify();
268    }
269
270    fn focus_completions(
271        &mut self,
272        _: &FocusPredictions,
273        window: &mut Window,
274        cx: &mut Context<Self>,
275    ) {
276        cx.focus_self(window);
277        cx.notify();
278    }
279
280    fn preview_completion(
281        &mut self,
282        _: &PreviewPrediction,
283        window: &mut Window,
284        cx: &mut Context<Self>,
285    ) {
286        let completion = self
287            .ep_store
288            .read(cx)
289            .rateable_predictions()
290            .skip(self.selected_index)
291            .take(1)
292            .next()
293            .cloned();
294
295        self.select_completion(completion, false, window, cx);
296    }
297
298    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
299        let completion = self
300            .ep_store
301            .read(cx)
302            .rateable_predictions()
303            .skip(self.selected_index)
304            .take(1)
305            .next()
306            .cloned();
307
308        self.select_completion(completion, true, window, cx);
309    }
310
311    fn update_buffer_diff(
312        diff: &Entity<BufferDiff>,
313        new_buffer_snapshot: BufferSnapshot,
314        old_buffer_snapshot: BufferSnapshot,
315        cx: &mut App,
316    ) -> Task<()> {
317        diff.update(cx, |diff, cx| {
318            diff.set_base_text(
319                Some(old_buffer_snapshot.text().into()),
320                new_buffer_snapshot.text,
321                cx,
322            )
323        })
324    }
325
326    fn insert_editable_region_markers(
327        editor: &Entity<Editor>,
328        buffer: &Entity<Buffer>,
329        marker_range: Range<usize>,
330        cx: &mut Context<Self>,
331    ) {
332        editor.update(cx, |editor, cx| {
333            let buffer_snapshot = buffer.read(cx).snapshot();
334            let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
335            let start_buffer_anchor = buffer_snapshot
336                .anchor_after(buffer_snapshot.clip_offset(marker_range.start, Bias::Left));
337            let end_buffer_anchor = buffer_snapshot
338                .anchor_after(buffer_snapshot.clip_offset(marker_range.end, Bias::Right));
339            let Some(start_anchor) = multibuffer_snapshot.anchor_in_excerpt(start_buffer_anchor)
340            else {
341                return;
342            };
343            let Some(end_anchor) = multibuffer_snapshot.anchor_in_excerpt(end_buffer_anchor) else {
344                return;
345            };
346            let Some((start_hint_position, _)) =
347                multibuffer_snapshot.anchor_to_buffer_anchor(start_anchor)
348            else {
349                return;
350            };
351            let Some((end_hint_position, _)) =
352                multibuffer_snapshot.anchor_to_buffer_anchor(end_anchor)
353            else {
354                return;
355            };
356
357            editor.splice_inlays(
358                &[InlayId::Hint(0), InlayId::Hint(1)],
359                vec![
360                    Inlay::hint(
361                        InlayId::Hint(0),
362                        start_anchor,
363                        &InlayHint {
364                            position: start_hint_position,
365                            label: InlayHintLabel::String("╭─ editable region start\n".into()),
366                            kind: Some(InlayHintKind::Parameter),
367                            padding_left: false,
368                            padding_right: false,
369                            tooltip: None,
370                            resolve_state: ResolveState::Resolved,
371                        },
372                    ),
373                    Inlay::hint(
374                        InlayId::Hint(1),
375                        end_anchor,
376                        &InlayHint {
377                            position: end_hint_position,
378                            label: InlayHintLabel::String("\n╰─ editable region end".into()),
379                            kind: Some(InlayHintKind::Parameter),
380                            padding_left: false,
381                            padding_right: false,
382                            tooltip: None,
383                            resolve_state: ResolveState::Resolved,
384                        },
385                    ),
386                ],
387                cx,
388            );
389        });
390    }
391
392    fn expected_patch_for_active(&self, cx: &App) -> Option<String> {
393        let active_prediction = self.active_prediction.as_ref()?;
394        let expected_text = active_prediction.expected_buffer.read(cx).snapshot().text();
395        let original_text = active_prediction.prediction.snapshot.text();
396        let diff_body = language::unified_diff(&original_text, &expected_text);
397
398        if diff_body.is_empty() {
399            return None;
400        }
401
402        let path = active_prediction
403            .prediction
404            .snapshot
405            .file()
406            .map(|file| file.path().as_unix_str());
407        let header = match path {
408            Some(path) => format!("--- a/{path}\n+++ b/{path}\n"),
409            None => String::new(),
410        };
411
412        Some(format!("{header}{diff_body}"))
413    }
414
415    fn write_formatted_inputs(formatted_inputs: &mut String, inputs: &EditPredictionInputs) {
416        match inputs {
417            EditPredictionInputs::V2(inputs) => {
418                Self::write_events(formatted_inputs, &inputs.events);
419                Self::write_related_files(
420                    formatted_inputs,
421                    inputs.related_files.as_deref().unwrap_or_default(),
422                );
423                Self::write_cursor_excerpt(
424                    formatted_inputs,
425                    inputs.cursor_path.as_ref(),
426                    inputs.cursor_excerpt.as_ref(),
427                    inputs.cursor_offset_in_excerpt,
428                );
429            }
430            EditPredictionInputs::V3(inputs) => {
431                Self::write_events(formatted_inputs, &inputs.events);
432                Self::write_related_files(formatted_inputs, &inputs.editable_context);
433                Self::write_zeta3_cursor_excerpt(formatted_inputs, inputs);
434            }
435        }
436    }
437
438    fn write_events(formatted_inputs: &mut String, events: &[Arc<zeta_prompt::Event>]) {
439        write!(formatted_inputs, "## Events\n\n").unwrap();
440
441        for event in events {
442            formatted_inputs.push_str("```diff\n");
443            zeta_prompt::write_event(formatted_inputs, event.as_ref());
444            formatted_inputs.push_str("```\n\n");
445        }
446    }
447
448    fn write_related_files(formatted_inputs: &mut String, included_files: &[RelatedFile]) {
449        write!(formatted_inputs, "## Related files\n\n").unwrap();
450
451        for included_file in included_files {
452            write!(formatted_inputs, "### {}\n\n", included_file.path.display()).unwrap();
453
454            for excerpt in included_file.excerpts.iter() {
455                write!(
456                    formatted_inputs,
457                    "```{}\n{}\n```\n",
458                    included_file.path.display(),
459                    excerpt.text
460                )
461                .unwrap();
462            }
463        }
464    }
465
466    fn write_zeta3_cursor_excerpt(formatted_inputs: &mut String, inputs: &Zeta3PromptInput) {
467        let current_excerpt = inputs
468            .editable_context
469            .iter()
470            .filter(|file| file.path == inputs.cursor_path)
471            .flat_map(|file| file.excerpts.iter())
472            .find_map(|excerpt| {
473                if excerpt.context_source != ContextSource::CurrentFile {
474                    return None;
475                }
476
477                Some((
478                    excerpt,
479                    Self::offset_for_position_in_excerpt(excerpt, inputs.cursor_position)?,
480                ))
481            });
482
483        if let Some((excerpt, cursor_offset)) = current_excerpt {
484            Self::write_cursor_excerpt(
485                formatted_inputs,
486                inputs.cursor_path.as_ref(),
487                excerpt.text.as_ref(),
488                cursor_offset,
489            );
490        } else {
491            write!(formatted_inputs, "## Cursor Excerpt\n\n").unwrap();
492            writeln!(
493                formatted_inputs,
494                "No current-file excerpt found for `{}` at row {}, column {}.",
495                inputs.cursor_path.display(),
496                inputs.cursor_position.row,
497                inputs.cursor_position.column
498            )
499            .unwrap();
500        }
501    }
502
503    fn write_cursor_excerpt(
504        formatted_inputs: &mut String,
505        cursor_path: &Path,
506        cursor_excerpt: &str,
507        cursor_offset: usize,
508    ) {
509        write!(formatted_inputs, "## Cursor Excerpt\n\n").unwrap();
510
511        let mut cursor_offset = cursor_offset.min(cursor_excerpt.len());
512        while !cursor_excerpt.is_char_boundary(cursor_offset) {
513            cursor_offset = cursor_offset.saturating_sub(1);
514        }
515        writeln!(
516            formatted_inputs,
517            "```{}\n{}<CURSOR>{}\n```\n",
518            cursor_path.display(),
519            &cursor_excerpt[..cursor_offset],
520            &cursor_excerpt[cursor_offset..],
521        )
522        .unwrap();
523    }
524
525    fn offset_for_position_in_excerpt(
526        excerpt: &RelatedExcerpt,
527        position: FilePosition,
528    ) -> Option<usize> {
529        if position.row < excerpt.row_range.start {
530            return None;
531        }
532
533        let relative_row = (position.row - excerpt.row_range.start) as usize;
534        let text = excerpt.text.as_ref();
535        let mut row_start = 0;
536
537        for row in 0..=relative_row {
538            if row == relative_row {
539                let row_end = text[row_start..]
540                    .find('\n')
541                    .map_or(text.len(), |offset| row_start + offset);
542                let row_text = &text[row_start..row_end];
543                let column =
544                    row_text.floor_char_boundary((position.column as usize).min(row_text.len()));
545                return Some(row_start + column);
546            }
547
548            row_start += text[row_start..].find('\n')? + 1;
549        }
550
551        None
552    }
553
554    pub fn select_completion(
555        &mut self,
556        prediction: Option<EditPrediction>,
557        focus: bool,
558        window: &mut Window,
559        cx: &mut Context<Self>,
560    ) {
561        // Avoid resetting completion rating if it's already selected.
562        if let Some(prediction) = prediction {
563            self.selected_index = self
564                .ep_store
565                .read(cx)
566                .rateable_predictions()
567                .enumerate()
568                .find(|(_, completion_b)| prediction.id == completion_b.id)
569                .map(|(ix, _)| ix)
570                .unwrap_or(self.selected_index);
571            cx.notify();
572
573            if let Some(prev_prediction) = self.active_prediction.as_ref()
574                && prediction.id == prev_prediction.prediction.id
575            {
576                if focus {
577                    window.focus(&prev_prediction.feedback_editor.focus_handle(cx), cx);
578                }
579                return;
580            }
581
582            let editable_range = prediction.editable_range.clone().or_else(|| {
583                Some(prediction.edits.first()?.0.start..prediction.edits.last()?.0.end)
584            });
585            let predicted_buffer = prediction.edit_preview.build_result_buffer(cx);
586            let predicted_buffer_snapshot = predicted_buffer.read(cx).snapshot();
587            let visible_range = prediction
588                .edit_preview
589                .compute_visible_range(&prediction.edits)
590                .or_else(|| {
591                    editable_range.as_ref().map(|range| {
592                        range.start.to_point(&prediction.snapshot)
593                            ..range.end.to_point(&prediction.snapshot)
594                    })
595                })
596                .unwrap_or(Point::zero()..Point::zero());
597            let visible_range_with_context =
598                Point::new(visible_range.start.row.saturating_sub(5), 0)
599                    ..Point::new(visible_range.end.row.saturating_add(5), 0)
600                        .min(predicted_buffer_snapshot.max_point());
601            let predicted_diff_task = self.diff_editor.update(cx, |editor, cx| {
602                let predicted_buffer_id = predicted_buffer_snapshot.remote_id();
603                let diff = cx.new(|cx| {
604                    BufferDiff::new(
605                        &predicted_buffer_snapshot.text,
606                        predicted_buffer_snapshot.language().cloned(),
607                        predicted_buffer.read(cx).language_registry(),
608                        cx,
609                    )
610                });
611                let predicted_diff_task = Self::update_buffer_diff(
612                    &diff,
613                    predicted_buffer_snapshot.clone(),
614                    prediction.snapshot.clone(),
615                    cx,
616                );
617
618                editor.disable_header_for_buffer(predicted_buffer_id, cx);
619                editor.buffer().update(cx, |multibuffer, cx| {
620                    multibuffer.clear(cx);
621                    multibuffer.set_excerpts_for_buffer(
622                        predicted_buffer.clone(),
623                        [visible_range_with_context],
624                        0,
625                        cx,
626                    );
627                    multibuffer.add_diff(diff, cx);
628                });
629                predicted_diff_task
630            });
631
632            if let Some(editable_range) = editable_range.as_ref() {
633                Self::insert_editable_region_markers(
634                    &self.diff_editor,
635                    &predicted_buffer,
636                    prediction
637                        .edit_preview
638                        .anchor_to_offset_in_result(editable_range.start)
639                        ..prediction
640                            .edit_preview
641                            .anchor_to_offset_in_result(editable_range.end),
642                    cx,
643                );
644            }
645
646            self.diff_editor.update(cx, |editor, cx| {
647                if let Some(cursor_position) = prediction.cursor_position.as_ref() {
648                    let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
649                    let cursor_offset = prediction
650                        .edit_preview
651                        .anchor_to_offset_in_result(cursor_position.anchor)
652                        + cursor_position.offset;
653                    let predicted_buffer_snapshot = predicted_buffer.read(cx).snapshot();
654                    let cursor_anchor = predicted_buffer_snapshot.anchor_after(
655                        predicted_buffer_snapshot.clip_offset(cursor_offset, Bias::Right),
656                    );
657
658                    if let Some(anchor) = multibuffer_snapshot.anchor_in_excerpt(cursor_anchor) {
659                        editor.splice_inlays(
660                            &[InlayId::EditPrediction(0)],
661                            vec![Inlay::edit_prediction(0, anchor, "▏")],
662                            cx,
663                        );
664                    }
665                }
666            });
667
668            let mut formatted_inputs = String::new();
669            Self::write_formatted_inputs(&mut formatted_inputs, &prediction.inputs);
670
671            let current_editable_region = editable_range.as_ref().map(|range| {
672                prediction
673                    .buffer
674                    .read(cx)
675                    .snapshot()
676                    .text_for_range(range.clone())
677                    .collect::<String>()
678            });
679            let expected_buffer = cx.new(|cx| {
680                let mut buffer = Buffer::local(prediction.snapshot.text(), cx);
681                buffer.set_language_async(prediction.snapshot.language().cloned(), cx);
682                buffer
683            });
684            let expected_editable_range = editable_range.as_ref().map(|editable_range| {
685                expected_buffer.update(cx, |buffer, cx| {
686                    let snapshot = buffer.snapshot();
687                    let editable_point_range = editable_range.start.to_point(&prediction.snapshot)
688                        ..editable_range.end.to_point(&prediction.snapshot);
689                    let expected_editable_range = snapshot.anchor_before(editable_point_range.start)
690                        ..snapshot.anchor_after(editable_point_range.end);
691                    if let Some(current_editable_region) = current_editable_region {
692                        buffer.edit(
693                            [(expected_editable_range.clone(), current_editable_region)],
694                            None,
695                            cx,
696                        );
697                    }
698                    expected_editable_range
699                })
700            });
701            let expected_buffer_snapshot = expected_buffer.read(cx).snapshot();
702            let expected_excerpt_range = expected_editable_range
703                .as_ref()
704                .map(|range| {
705                    range.start.to_point(&expected_buffer_snapshot)
706                        ..range.end.to_point(&expected_buffer_snapshot)
707                })
708                .unwrap_or(visible_range);
709            let expected_diff = cx.new(|cx| {
710                BufferDiff::new(
711                    &expected_buffer_snapshot.text,
712                    expected_buffer_snapshot.language().cloned(),
713                    expected_buffer.read(cx).language_registry(),
714                    cx,
715                )
716            });
717            let expected_diff_task = Self::update_buffer_diff(
718                &expected_diff,
719                expected_buffer_snapshot.clone(),
720                prediction.snapshot.clone(),
721                cx,
722            );
723            let expected_editor = cx.new(|cx| {
724                let multibuffer = cx.new(|cx| {
725                    let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
726                    multibuffer.set_excerpts_for_buffer(
727                        expected_buffer.clone(),
728                        [expected_excerpt_range],
729                        0,
730                        cx,
731                    );
732                    multibuffer.add_diff(expected_diff.clone(), cx);
733                    multibuffer
734                });
735                let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
736                let expected_buffer_id = expected_buffer.read(cx).remote_id();
737                editor.disable_header_for_buffer(expected_buffer_id, cx);
738                editor.disable_inline_diagnostics();
739                editor.set_expand_all_diff_hunks(cx);
740                editor.set_show_git_diff_gutter(false, cx);
741                editor.set_show_code_actions(false, cx);
742                editor.set_show_runnables(false, cx);
743                editor.set_show_bookmarks(false, cx);
744                editor.set_show_breakpoints(false, cx);
745                editor.set_show_wrap_guides(false, cx);
746                editor.set_show_edit_predictions(Some(false), window, cx);
747                editor
748            });
749            if let Some(expected_editable_range) = expected_editable_range.as_ref() {
750                let expected_buffer_snapshot = expected_buffer.read(cx).snapshot();
751                Self::insert_editable_region_markers(
752                    &expected_editor,
753                    &expected_buffer,
754                    expected_editable_range
755                        .start
756                        .to_offset(&expected_buffer_snapshot)
757                        ..expected_editable_range
758                            .end
759                            .to_offset(&expected_buffer_snapshot),
760                    cx,
761                );
762            }
763
764            let expected_buffer_subscription = cx.subscribe(&expected_buffer, {
765                let expected_diff = expected_diff.clone();
766                let original_snapshot = prediction.snapshot.clone();
767                move |this, buffer, event, cx| match event {
768                    language::BufferEvent::Edited { .. }
769                    | language::BufferEvent::LanguageChanged(_)
770                    | language::BufferEvent::Reparsed => {
771                        let task = Self::update_buffer_diff(
772                            &expected_diff,
773                            buffer.read(cx).snapshot(),
774                            original_snapshot.clone(),
775                            cx,
776                        );
777                        if let Some(active_prediction) = this.active_prediction.as_mut() {
778                            active_prediction.expected_diff_task = task;
779                        }
780                    }
781                    _ => {}
782                }
783            });
784
785            self.active_prediction = Some(ActivePrediction {
786                prediction,
787                feedback_editor: cx.new(|cx| {
788                    let mut editor = Editor::multi_line(window, cx);
789                    editor.disable_scrollbars_and_minimap(window, cx);
790                    editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
791                    editor.set_show_line_numbers(false, cx);
792                    editor.set_show_git_diff_gutter(false, cx);
793                    editor.set_show_code_actions(false, cx);
794                    editor.set_show_runnables(false, cx);
795                    editor.set_show_bookmarks(false, cx);
796                    editor.set_show_breakpoints(false, cx);
797                    editor.set_show_wrap_guides(false, cx);
798                    editor.set_show_indent_guides(false, cx);
799                    editor.set_show_edit_predictions(Some(false), window, cx);
800                    editor.set_placeholder_text("Add your feedback…", window, cx);
801                    editor.set_completion_provider(Some(Rc::new(FeedbackCompletionProvider)));
802                    if focus {
803                        cx.focus_self(window);
804                    }
805                    editor
806                }),
807                expected_buffer,
808                expected_editor,
809                _expected_buffer_subscription: expected_buffer_subscription,
810                _predicted_diff_task: predicted_diff_task,
811                expected_diff_task,
812                formatted_inputs: cx.new(|cx| {
813                    Markdown::new(
814                        formatted_inputs.into(),
815                        Some(self.language_registry.clone()),
816                        None,
817                        cx,
818                    )
819                }),
820            });
821        } else {
822            self.active_prediction = None;
823        }
824
825        cx.notify();
826    }
827
828    fn render_view_nav(&self, cx: &Context<Self>) -> impl IntoElement {
829        h_flex()
830            .h_8()
831            .px_1()
832            .border_b_1()
833            .border_color(cx.theme().colors().border)
834            .bg(cx.theme().colors().elevated_surface_background)
835            .gap_1()
836            .child(
837                Button::new(
838                    ElementId::Name("suggested-edits".into()),
839                    RatePredictionView::SuggestedEdits.name(),
840                )
841                .label_size(LabelSize::Small)
842                .on_click(cx.listener(move |this, _, _window, cx| {
843                    this.current_view = RatePredictionView::SuggestedEdits;
844                    cx.notify();
845                }))
846                .toggle_state(self.current_view == RatePredictionView::SuggestedEdits),
847            )
848            .child(
849                Button::new(
850                    ElementId::Name("raw-input".into()),
851                    RatePredictionView::RawInput.name(),
852                )
853                .label_size(LabelSize::Small)
854                .on_click(cx.listener(move |this, _, _window, cx| {
855                    this.current_view = RatePredictionView::RawInput;
856                    cx.notify();
857                }))
858                .toggle_state(self.current_view == RatePredictionView::RawInput),
859            )
860    }
861
862    fn render_suggested_edits(&self, cx: &mut Context<Self>) -> Option<gpui::Stateful<Div>> {
863        let bg_color = cx.theme().colors().editor_background;
864        let border_color = cx.theme().colors().border;
865        let active_prediction = self.active_prediction.as_ref()?;
866
867        Some(
868            v_flex()
869                .id("diff")
870                .size_full()
871                .bg(bg_color)
872                .overflow_hidden()
873                .child(
874                    v_flex()
875                        .flex_1()
876                        .min_h_0()
877                        .child(
878                            h_flex()
879                                .h_8()
880                                .px_2()
881                                .border_b_1()
882                                .border_color(border_color)
883                                .child(Label::new("Predicted Patch").size(LabelSize::Small)),
884                        )
885                        .child(
886                            div()
887                                .id("predicted-patch-diff")
888                                .p_4()
889                                .flex_1()
890                                .min_h_0()
891                                .overflow_scroll()
892                                .whitespace_nowrap()
893                                .child(self.diff_editor.clone()),
894                        ),
895                )
896                .child(
897                    v_flex()
898                        .flex_1()
899                        .min_h_0()
900                        .border_t_1()
901                        .border_color(border_color)
902                        .child(
903                            h_flex()
904                                .h_8()
905                                .px_2()
906                                .gap_2()
907                                .border_b_1()
908                                .border_color(border_color)
909                                .child(Label::new("Expected Patch").size(LabelSize::Small)),
910                        )
911                        .child(
912                            div()
913                                .id("expected-patch")
914                                .p_4()
915                                .flex_1()
916                                .min_h_0()
917                                .overflow_scroll()
918                                .whitespace_nowrap()
919                                .child(active_prediction.expected_editor.clone()),
920                        ),
921                ),
922        )
923    }
924
925    fn render_raw_input(
926        &self,
927        window: &mut Window,
928        cx: &mut Context<Self>,
929    ) -> Option<gpui::Stateful<Div>> {
930        let theme_settings = ThemeSettings::get_global(cx);
931        let buffer_font_size = theme_settings.buffer_font_size(cx);
932
933        Some(
934            v_flex()
935                .size_full()
936                .overflow_hidden()
937                .relative()
938                .child(
939                    div()
940                        .id("raw-input")
941                        .py_4()
942                        .px_6()
943                        .size_full()
944                        .bg(cx.theme().colors().editor_background)
945                        .overflow_scroll()
946                        .child(if let Some(active_prediction) = &self.active_prediction {
947                            markdown::MarkdownElement::new(
948                                active_prediction.formatted_inputs.clone(),
949                                MarkdownStyle {
950                                    base_text_style: window.text_style(),
951                                    syntax: cx.theme().syntax().clone(),
952                                    code_block: StyleRefinement {
953                                        text: TextStyleRefinement {
954                                            font_family: Some(
955                                                theme_settings.buffer_font.family.clone(),
956                                            ),
957                                            font_size: Some(buffer_font_size.into()),
958                                            ..Default::default()
959                                        },
960                                        padding: EdgesRefinement {
961                                            top: Some(DefiniteLength::Absolute(
962                                                AbsoluteLength::Pixels(px(8.)),
963                                            )),
964                                            left: Some(DefiniteLength::Absolute(
965                                                AbsoluteLength::Pixels(px(8.)),
966                                            )),
967                                            right: Some(DefiniteLength::Absolute(
968                                                AbsoluteLength::Pixels(px(8.)),
969                                            )),
970                                            bottom: Some(DefiniteLength::Absolute(
971                                                AbsoluteLength::Pixels(px(8.)),
972                                            )),
973                                        },
974                                        margin: EdgesRefinement {
975                                            top: Some(Length::Definite(px(8.).into())),
976                                            left: Some(Length::Definite(px(0.).into())),
977                                            right: Some(Length::Definite(px(0.).into())),
978                                            bottom: Some(Length::Definite(px(12.).into())),
979                                        },
980                                        border_style: Some(BorderStyle::Solid),
981                                        border_widths: EdgesRefinement {
982                                            top: Some(AbsoluteLength::Pixels(px(1.))),
983                                            left: Some(AbsoluteLength::Pixels(px(1.))),
984                                            right: Some(AbsoluteLength::Pixels(px(1.))),
985                                            bottom: Some(AbsoluteLength::Pixels(px(1.))),
986                                        },
987                                        border_color: Some(cx.theme().colors().border_variant),
988                                        background: Some(
989                                            cx.theme().colors().editor_background.into(),
990                                        ),
991                                        ..Default::default()
992                                    },
993                                    ..Default::default()
994                                },
995                            )
996                            .into_any_element()
997                        } else {
998                            div()
999                                .child("No active completion".to_string())
1000                                .into_any_element()
1001                        }),
1002                )
1003                .id("raw-input-view"),
1004        )
1005    }
1006
1007    fn render_active_completion(
1008        &mut self,
1009        window: &mut Window,
1010        cx: &mut Context<Self>,
1011    ) -> Option<impl IntoElement> {
1012        let active_prediction = self.active_prediction.as_ref()?;
1013        let completion_id = active_prediction.prediction.id.clone();
1014        let focus_handle = &self.focus_handle(cx);
1015
1016        let border_color = cx.theme().colors().border;
1017        let bg_color = cx.theme().colors().editor_background;
1018
1019        let rated = self.ep_store.read(cx).is_prediction_rated(&completion_id);
1020        let feedback_empty = active_prediction
1021            .feedback_editor
1022            .read(cx)
1023            .text(cx)
1024            .is_empty();
1025
1026        let label_container = h_flex().pl_1().gap_1p5();
1027
1028        Some(
1029            v_flex()
1030                .size_full()
1031                .overflow_hidden()
1032                .relative()
1033                .child(
1034                    v_flex()
1035                        .size_full()
1036                        .overflow_hidden()
1037                        .relative()
1038                        .child(self.render_view_nav(cx))
1039                        .when_some(
1040                            match self.current_view {
1041                                RatePredictionView::SuggestedEdits => {
1042                                    self.render_suggested_edits(cx)
1043                                }
1044                                RatePredictionView::RawInput => self.render_raw_input(window, cx),
1045                            },
1046                            |this, element| this.child(element),
1047                        ),
1048                )
1049                .when(!rated, |this| {
1050                    let modal = cx.entity().downgrade();
1051                    let failure_mode_menu =
1052                        ContextMenu::build(window, cx, move |menu, _window, _cx| {
1053                            FeedbackCompletionProvider::FAILURE_MODES
1054                                .iter()
1055                                .fold(menu, |menu, (key, description)| {
1056                                    let key: SharedString = (*key).into();
1057                                    let description: SharedString = (*description).into();
1058                                    let modal = modal.clone();
1059                                    menu.entry(
1060                                        format!("{} {}", key, description),
1061                                        None,
1062                                        move |window, cx| {
1063                                            if let Some(modal) = modal.upgrade() {
1064                                                modal.update(cx, |this, cx| {
1065                                                    if let Some(active) = &this.active_prediction {
1066                                                        active.feedback_editor.update(
1067                                                            cx,
1068                                                            |editor, cx| {
1069                                                                editor.set_text(
1070                                                                    format!("{} {}", key, description),
1071                                                                    window,
1072                                                                    cx,
1073                                                                );
1074                                                            },
1075                                                        );
1076                                                    }
1077                                                });
1078                                            }
1079                                        },
1080                                    )
1081                                })
1082                        });
1083
1084                    this.child(
1085                        h_flex()
1086                            .p_2()
1087                            .gap_2()
1088                            .border_y_1()
1089                            .border_color(border_color)
1090                            .child(
1091                                DropdownMenu::new(
1092                                        "failure-mode-dropdown",
1093                                        "Issue",
1094                                        failure_mode_menu,
1095                                    )
1096                                    .handle(self.failure_mode_menu_handle.clone())
1097                                    .style(ui::DropdownStyle::Outlined)
1098                                    .trigger_size(ButtonSize::Compact),
1099                            )
1100                            .child(
1101                                h_flex()
1102                                    .gap_2()
1103                                    .child(
1104                                        Icon::new(IconName::Info)
1105                                            .size(IconSize::XSmall)
1106                                            .color(Color::Muted),
1107                                    )
1108                                    .child(
1109                                        div().flex_wrap().child(
1110                                            Label::new(concat!(
1111                                                "Explain why this completion is good or bad. ",
1112                                                "If it's negative, describe what you expected instead."
1113                                            ))
1114                                            .size(LabelSize::Small)
1115                                            .color(Color::Muted),
1116                                        ),
1117                                    ),
1118                            ),
1119                    )
1120                })
1121                .when(!rated, |this| {
1122                    this.child(
1123                        div()
1124                            .h_40()
1125                            .pt_1()
1126                            .bg(bg_color)
1127                            .child(active_prediction.feedback_editor.clone()),
1128                    )
1129                })
1130                .child(
1131                    h_flex()
1132                        .p_1()
1133                        .h_8()
1134                        .max_h_8()
1135                        .border_t_1()
1136                        .border_color(border_color)
1137                        .max_w_full()
1138                        .justify_between()
1139                        .children(if rated {
1140                            Some(
1141                                label_container
1142                                    .child(
1143                                        Icon::new(IconName::Check)
1144                                            .size(IconSize::Small)
1145                                            .color(Color::Success),
1146                                    )
1147                                    .child(Label::new("Rated completion.").color(Color::Muted)),
1148                            )
1149                        } else if active_prediction.prediction.edits.is_empty() {
1150                            Some(
1151                                label_container
1152                                    .child(
1153                                        Icon::new(IconName::Warning)
1154                                            .size(IconSize::Small)
1155                                            .color(Color::Warning),
1156                                    )
1157                                    .child(Label::new("No edits produced.").color(Color::Muted)),
1158                            )
1159                        } else {
1160                            Some(label_container)
1161                        })
1162                        .child(
1163                            h_flex()
1164                                .gap_1()
1165                                .child(
1166                                    Button::new("bad", "Bad Prediction")
1167                                        .start_icon(Icon::new(IconName::ThumbsDown).size(IconSize::Small))
1168                                        .disabled(rated || feedback_empty)
1169                                        .when(feedback_empty, |this| {
1170                                            this.tooltip(Tooltip::text(
1171                                                "Explain what's bad about it before reporting it",
1172                                            ))
1173                                        })
1174                                        .key_binding(KeyBinding::for_action_in(
1175                                            &ThumbsDownActivePrediction,
1176                                            focus_handle,
1177                                            cx,
1178                                        ))
1179                                        .on_click(cx.listener(move |this, _, window, cx| {
1180                                            if this.active_prediction.is_some() {
1181                                                this.thumbs_down_active(
1182                                                    &ThumbsDownActivePrediction,
1183                                                    window,
1184                                                    cx,
1185                                                );
1186                                            }
1187                                        })),
1188                                )
1189                                .child(
1190                                    Button::new("good", "Good Prediction")
1191                                        .start_icon(Icon::new(IconName::ThumbsUp).size(IconSize::Small))
1192                                        .disabled(rated)
1193                                        .key_binding(KeyBinding::for_action_in(
1194                                            &ThumbsUpActivePrediction,
1195                                            focus_handle,
1196                                            cx,
1197                                        ))
1198                                        .on_click(cx.listener(move |this, _, window, cx| {
1199                                            if this.active_prediction.is_some() {
1200                                                this.thumbs_up_active(
1201                                                    &ThumbsUpActivePrediction,
1202                                                    window,
1203                                                    cx,
1204                                                );
1205                                            }
1206                                        })),
1207                                ),
1208                        ),
1209                ),
1210        )
1211    }
1212
1213    fn render_shown_completions(&self, cx: &Context<Self>) -> impl Iterator<Item = ListItem> {
1214        self.ep_store
1215            .read(cx)
1216            .rateable_predictions()
1217            .cloned()
1218            .enumerate()
1219            .map(|(index, completion)| {
1220                let selected = self
1221                    .active_prediction
1222                    .as_ref()
1223                    .is_some_and(|selected| selected.prediction.id == completion.id);
1224                let rated = self.ep_store.read(cx).is_prediction_rated(&completion.id);
1225
1226                let (icon_name, icon_color, tooltip_text) =
1227                    match (rated, completion.edits.is_empty()) {
1228                        (true, _) => (IconName::Check, Color::Success, "Rated Prediction"),
1229                        (false, true) => (IconName::File, Color::Muted, "No Edits Produced"),
1230                        (false, false) => (IconName::FileDiff, Color::Accent, "Edits Available"),
1231                    };
1232                let (trigger_icon, trigger_tooltip) = match completion.trigger {
1233                    PredictEditsRequestTrigger::Testing => (IconName::Debug, "Testing"),
1234                    PredictEditsRequestTrigger::Diagnostics => {
1235                        (IconName::ToolDiagnostics, "Diagnostics")
1236                    }
1237                    PredictEditsRequestTrigger::DiagnosticNavigation => {
1238                        (IconName::ArrowRight, "Diagnostic Navigation")
1239                    }
1240                    PredictEditsRequestTrigger::Cli => (IconName::Terminal, "CLI"),
1241                    PredictEditsRequestTrigger::Explicit => (IconName::Person, "Explicit"),
1242                    PredictEditsRequestTrigger::BufferEdit => (IconName::Pencil, "Buffer Edit"),
1243                    PredictEditsRequestTrigger::LSPCompletionAccepted => {
1244                        (IconName::Code, "LSP Completion Accepted")
1245                    }
1246                    PredictEditsRequestTrigger::PredictionAccepted => {
1247                        (IconName::OmegaPredict, "Prediction Accepted")
1248                    }
1249                    PredictEditsRequestTrigger::PredictionPartiallyAccepted => {
1250                        (IconName::CheckDouble, "Prediction Partially Accepted")
1251                    }
1252                    PredictEditsRequestTrigger::EditorCreated => (IconName::File, "Editor Created"),
1253                    PredictEditsRequestTrigger::ProviderChanged => {
1254                        (IconName::Settings, "Provider Changed")
1255                    }
1256                    PredictEditsRequestTrigger::UserInfoChanged => {
1257                        (IconName::Person, "User Info Changed")
1258                    }
1259                    PredictEditsRequestTrigger::VimModeChanged => {
1260                        (IconName::Keyboard, "Vim Mode Changed")
1261                    }
1262                    PredictEditsRequestTrigger::SettingsChanged => {
1263                        (IconName::Settings, "Settings Changed")
1264                    }
1265                    PredictEditsRequestTrigger::Other => (IconName::CircleHelp, "Other"),
1266                };
1267
1268                let file = completion.buffer.read(cx).file();
1269                let file_name = file.as_ref().map_or(
1270                    SharedString::new_static(MultiBuffer::DEFAULT_TITLE),
1271                    |file| file.file_name(cx).to_string().into(),
1272                );
1273                let file_path = file.map(|file| file.path().as_unix_str().to_string());
1274
1275                ListItem::new(completion.id.clone())
1276                    .inset(true)
1277                    .spacing(ListItemSpacing::Sparse)
1278                    .focused(index == self.selected_index)
1279                    .toggle_state(selected)
1280                    .child(
1281                        h_flex()
1282                            .id("completion-content")
1283                            .gap_3()
1284                            .child(Icon::new(icon_name).color(icon_color).size(IconSize::Small))
1285                            .child(
1286                                Icon::new(trigger_icon)
1287                                    .color(Color::Muted)
1288                                    .size(IconSize::XSmall),
1289                            )
1290                            .child(
1291                                v_flex().child(
1292                                    h_flex()
1293                                        .gap_1()
1294                                        .child(Label::new(file_name).size(LabelSize::Small))
1295                                        .when_some(file_path, |this, p| {
1296                                            this.child(
1297                                                Label::new(p)
1298                                                    .size(LabelSize::Small)
1299                                                    .color(Color::Muted),
1300                                            )
1301                                        }),
1302                                ),
1303                            ),
1304                    )
1305                    .tooltip(Tooltip::text(format!(
1306                        "{tooltip_text} • Trigger: {trigger_tooltip}"
1307                    )))
1308                    .on_click(cx.listener(move |this, _, window, cx| {
1309                        this.select_completion(Some(completion.clone()), true, window, cx);
1310                    }))
1311            })
1312    }
1313}
1314
1315impl Render for RatePredictionsModal {
1316    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1317        let border_color = cx.theme().colors().border;
1318
1319        h_flex()
1320            .key_context("RatePredictionModal")
1321            .track_focus(&self.focus_handle)
1322            .on_action(cx.listener(Self::dismiss))
1323            .on_action(cx.listener(Self::confirm))
1324            .on_action(cx.listener(Self::select_previous))
1325            .on_action(cx.listener(Self::select_prev_edit))
1326            .on_action(cx.listener(Self::select_next))
1327            .on_action(cx.listener(Self::select_next_edit))
1328            .on_action(cx.listener(Self::select_first))
1329            .on_action(cx.listener(Self::select_last))
1330            .on_action(cx.listener(Self::thumbs_up_active))
1331            .on_action(cx.listener(Self::thumbs_down_active))
1332            .on_action(cx.listener(Self::focus_completions))
1333            .on_action(cx.listener(Self::preview_completion))
1334            .bg(cx.theme().colors().elevated_surface_background)
1335            .border_1()
1336            .border_color(border_color)
1337            .w(window.viewport_size().width - px(320.))
1338            .h(window.viewport_size().height - px(300.))
1339            .rounded_lg()
1340            .shadow_lg()
1341            .child(
1342                v_flex()
1343                    .w_72()
1344                    .h_full()
1345                    .border_r_1()
1346                    .border_color(border_color)
1347                    .flex_shrink_0()
1348                    .overflow_hidden()
1349                    .child({
1350                        let icons = self.ep_store.read(cx).icons(cx);
1351                        h_flex()
1352                            .h_8()
1353                            .px_2()
1354                            .justify_between()
1355                            .border_b_1()
1356                            .border_color(border_color)
1357                            .child(Icon::new(icons.base).size(IconSize::Small))
1358                            .child(
1359                                Label::new("From most recent to oldest")
1360                                    .color(Color::Muted)
1361                                    .size(LabelSize::Small),
1362                            )
1363                    })
1364                    .child(
1365                        div()
1366                            .id("completion_list")
1367                            .p_0p5()
1368                            .h_full()
1369                            .overflow_y_scroll()
1370                            .child(
1371                                List::new()
1372                                    .empty_message(
1373                                        div()
1374                                            .p_2()
1375                                            .child(
1376                                                Label::new(concat!(
1377                                                    "No completions yet. ",
1378                                                    "Use the editor to generate some, ",
1379                                                    "and make sure to rate them!"
1380                                                ))
1381                                                .color(Color::Muted),
1382                                            )
1383                                            .into_any_element(),
1384                                    )
1385                                    .children(self.render_shown_completions(cx)),
1386                            ),
1387                    ),
1388            )
1389            .children(self.render_active_completion(window, cx))
1390            .on_mouse_down_out(cx.listener(|this, _, _, cx| {
1391                if !this.failure_mode_menu_handle.is_deployed() {
1392                    cx.emit(DismissEvent);
1393                }
1394            }))
1395    }
1396}
1397
1398impl EventEmitter<DismissEvent> for RatePredictionsModal {}
1399
1400impl Focusable for RatePredictionsModal {
1401    fn focus_handle(&self, _cx: &App) -> FocusHandle {
1402        self.focus_handle.clone()
1403    }
1404}
1405
1406impl ModalView for RatePredictionsModal {}
1407
1408struct FeedbackCompletionProvider;
1409
1410impl FeedbackCompletionProvider {
1411    const FAILURE_MODES: &'static [(&'static str, &'static str)] = &[
1412        ("@location", "Unexpected location"),
1413        ("@malformed", "Incomplete, cut off, or syntax error"),
1414        (
1415            "@deleted",
1416            "Deleted code that should be kept (use `@reverted` if it undid a recent edit)",
1417        ),
1418        ("@style", "Wrong coding style or conventions"),
1419        ("@repetitive", "Repeated existing code"),
1420        ("@hallucinated", "Referenced non-existent symbols"),
1421        ("@formatting", "Wrong indentation or structure"),
1422        ("@aggressive", "Changed more than expected"),
1423        ("@conservative", "Too cautious, changed too little"),
1424        ("@context", "Ignored or misunderstood context"),
1425        ("@reverted", "Undid recent edits"),
1426        ("@cursor_position", "Cursor placed in unhelpful position"),
1427        ("@whitespace", "Unwanted whitespace or newline changes"),
1428    ];
1429}
1430
1431impl editor::CompletionProvider for FeedbackCompletionProvider {
1432    fn completions(
1433        &self,
1434        buffer: &Entity<Buffer>,
1435        buffer_position: language::Anchor,
1436        _trigger: editor::CompletionContext,
1437        _window: &mut Window,
1438        cx: &mut Context<Editor>,
1439    ) -> gpui::Task<anyhow::Result<Vec<CompletionResponse>>> {
1440        let buffer = buffer.read(cx);
1441        let mut count_back = 0;
1442
1443        for char in buffer.reversed_chars_at(buffer_position) {
1444            if char.is_ascii_alphanumeric() || char == '_' || char == '@' {
1445                count_back += 1;
1446            } else {
1447                break;
1448            }
1449        }
1450
1451        let start_anchor = buffer.anchor_before(
1452            buffer_position
1453                .to_offset(&buffer)
1454                .saturating_sub(count_back),
1455        );
1456
1457        let replace_range = start_anchor..buffer_position;
1458        let snapshot = buffer.text_snapshot();
1459        let query: String = snapshot.text_for_range(replace_range.clone()).collect();
1460
1461        if !query.starts_with('@') {
1462            return gpui::Task::ready(Ok(vec![CompletionResponse {
1463                completions: vec![],
1464                display_options: CompletionDisplayOptions {
1465                    dynamic_width: true,
1466                },
1467                is_incomplete: false,
1468            }]));
1469        }
1470
1471        let query_lower = query.to_lowercase();
1472
1473        let completions: Vec<Completion> = Self::FAILURE_MODES
1474            .iter()
1475            .filter(|(key, _description)| key.starts_with(&query_lower))
1476            .map(|(key, description)| Completion {
1477                replace_range: replace_range.clone(),
1478                new_text: format!("{} {}", key, description),
1479                label: CodeLabel::plain(format!("{}: {}", key, description), None),
1480                documentation: None,
1481                source: CompletionSource::Custom,
1482                icon_path: None,
1483                icon_color: None,
1484                match_start: None,
1485                snippet_deduplication_key: None,
1486                insert_text_mode: None,
1487                confirm: None,
1488                group: None,
1489            })
1490            .collect();
1491
1492        gpui::Task::ready(Ok(vec![CompletionResponse {
1493            completions,
1494            display_options: CompletionDisplayOptions {
1495                dynamic_width: true,
1496            },
1497            is_incomplete: false,
1498        }]))
1499    }
1500
1501    fn is_completion_trigger(
1502        &self,
1503        _buffer: &Entity<Buffer>,
1504        _position: language::Anchor,
1505        text: &str,
1506        _trigger_in_words: bool,
1507        _cx: &mut Context<Editor>,
1508    ) -> bool {
1509        text.chars()
1510            .last()
1511            .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_' || c == '@')
1512    }
1513}
1514
Served at tenant.openagents/omega Member data and write actions are omitted.