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