1use std::sync::Arc;
2
3use crate::{ZedPredictUpsell, onboarding_event};
4use ai_onboarding::EditPredictionOnboarding;
5use client::{Client, UserStore};
6use db::kvp::Dismissable;
7use fs::Fs;
8use gpui::{
9 ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
10 linear_color_stop, linear_gradient,
11};
12use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
13use settings::update_settings_file;
14use ui::{Vector, VectorName, prelude::*};
15use workspace::{ModalView, Workspace};
16
17/// Introduces user to Zed's Edit Prediction feature
18pub struct ZedPredictModal {
19 onboarding: Entity<EditPredictionOnboarding>,
20 focus_handle: FocusHandle,
21}
22
23pub(crate) fn set_edit_prediction_provider(provider: EditPredictionProvider, cx: &mut App) {
24 let fs = <dyn Fs>::global(cx);
25 update_settings_file::<AllLanguageSettings>(fs, cx, move |settings, _| {
26 settings
27 .features
28 .get_or_insert(Default::default())
29 .edit_prediction_provider = Some(provider);
30 });
31}
32
33impl ZedPredictModal {
34 pub fn toggle(
35 workspace: &mut Workspace,
36 user_store: Entity<UserStore>,
37 client: Arc<Client>,
38 window: &mut Window,
39 cx: &mut Context<Workspace>,
40 ) {
41 workspace.toggle_modal(window, cx, |_window, cx| {
42 let weak_entity = cx.weak_entity();
43 Self {
44 onboarding: cx.new(|cx| {
45 EditPredictionOnboarding::new(
46 user_store.clone(),
47 client.clone(),
48 copilot::Copilot::global(cx)
49 .is_some_and(|copilot| copilot.read(cx).status().is_configured()),
50 Arc::new({
51 let this = weak_entity.clone();
52 move |_window, cx| {
53 ZedPredictUpsell::set_dismissed(true, cx);
54 set_edit_prediction_provider(EditPredictionProvider::Zed, cx);
55 this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
56 }
57 }),
58 Arc::new({
59 let this = weak_entity.clone();
60 move |window, cx| {
61 ZedPredictUpsell::set_dismissed(true, cx);
62 set_edit_prediction_provider(EditPredictionProvider::Copilot, cx);
63 this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
64 copilot::initiate_sign_in(window, cx);
65 }
66 }),
67 cx,
68 )
69 }),
70 focus_handle: cx.focus_handle(),
71 }
72 });
73 }
74
75 fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
76 ZedPredictUpsell::set_dismissed(true, cx);
77 cx.emit(DismissEvent);
78 }
79}
80
81impl EventEmitter<DismissEvent> for ZedPredictModal {}
82
83impl Focusable for ZedPredictModal {
84 fn focus_handle(&self, _cx: &App) -> FocusHandle {
85 self.focus_handle.clone()
86 }
87}
88
89impl ModalView for ZedPredictModal {
90 fn on_before_dismiss(
91 &mut self,
92 _window: &mut Window,
93 cx: &mut Context<Self>,
94 ) -> workspace::DismissDecision {
95 ZedPredictUpsell::set_dismissed(true, cx);
96 workspace::DismissDecision::Dismiss(true)
97 }
98}
99
100impl Render for ZedPredictModal {
101 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
102 let window_height = window.viewport_size().height;
103 let max_height = window_height - px(200.);
104
105 v_flex()
106 .id("edit-prediction-onboarding")
107 .key_context("ZedPredictModal")
108 .relative()
109 .w(px(550.))
110 .h_full()
111 .max_h(max_height)
112 .p_4()
113 .gap_2()
114 .elevation_3(cx)
115 .track_focus(&self.focus_handle(cx))
116 .overflow_hidden()
117 .on_action(cx.listener(Self::cancel))
118 .on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
119 onboarding_event!("Cancelled", trigger = "Action");
120 cx.emit(DismissEvent);
121 }))
122 .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
123 this.focus_handle.focus(window);
124 }))
125 .child(
126 div()
127 .opacity(0.5)
128 .absolute()
129 .top(px(-8.0))
130 .right_0()
131 .w(px(400.))
132 .h(px(92.))
133 .child(
134 Vector::new(VectorName::AiGrid, rems_from_px(400.), rems_from_px(92.))
135 .color(Color::Custom(cx.theme().colors().text.alpha(0.32))),
136 ),
137 )
138 .child(
139 div()
140 .absolute()
141 .top_0()
142 .right_0()
143 .w(px(660.))
144 .h(px(401.))
145 .overflow_hidden()
146 .bg(linear_gradient(
147 75.,
148 linear_color_stop(cx.theme().colors().panel_background.alpha(0.01), 1.0),
149 linear_color_stop(cx.theme().colors().panel_background, 0.45),
150 )),
151 )
152 .child(h_flex().absolute().top_2().right_2().child(
153 IconButton::new("cancel", IconName::Close).on_click(cx.listener(
154 |_, _: &ClickEvent, _window, cx| {
155 onboarding_event!("Cancelled", trigger = "X click");
156 cx.emit(DismissEvent);
157 },
158 )),
159 ))
160 .child(self.onboarding.clone())
161 }
162}