1mod base_keymap_picker;
2mod base_keymap_setting;
3
4use client::{telemetry::Telemetry, TelemetrySettings};
5use copilot_ui;
6use db::kvp::KEY_VALUE_STORE;
7use gpui::{
8 svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
9 ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
10 WindowContext,
11};
12use settings::{Settings, SettingsStore};
13use std::sync::Arc;
14use ui::{prelude::*, CheckboxWithLabel};
15use vim::VimModeSetting;
16use workspace::{
17 dock::DockPosition,
18 item::{Item, ItemEvent, TabContentParams},
19 open_new, AppState, Welcome, Workspace, WorkspaceId,
20};
21
22pub use base_keymap_setting::BaseKeymap;
23
24pub const FIRST_OPEN: &str = "first_open";
25
26pub fn init(cx: &mut AppContext) {
27 BaseKeymap::register(cx);
28
29 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
30 workspace.register_action(|workspace, _: &Welcome, cx| {
31 let welcome_page = WelcomePage::new(workspace, cx);
32 workspace.add_item_to_active_pane(Box::new(welcome_page), None, cx)
33 });
34 })
35 .detach();
36
37 base_keymap_picker::init(cx);
38}
39
40pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) {
41 open_new(app_state, cx, |workspace, cx| {
42 workspace.toggle_dock(DockPosition::Left, cx);
43 let welcome_page = WelcomePage::new(workspace, cx);
44 workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
45 cx.focus_view(&welcome_page);
46 cx.notify();
47 })
48 .detach();
49
50 db::write_and_log(cx, || {
51 KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
52 });
53}
54
55pub struct WelcomePage {
56 workspace: WeakView<Workspace>,
57 focus_handle: FocusHandle,
58 telemetry: Arc<Telemetry>,
59 _settings_subscription: Subscription,
60}
61
62impl Render for WelcomePage {
63 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
64 h_flex()
65 .size_full()
66 .bg(cx.theme().colors().editor_background)
67 .track_focus(&self.focus_handle)
68 .child(
69 v_flex()
70 .w_96()
71 .gap_4()
72 .mx_auto()
73 .child(
74 svg()
75 .path("icons/logo_96.svg")
76 .text_color(gpui::white())
77 .w(px(96.))
78 .h(px(96.))
79 .mx_auto(),
80 )
81 .child(
82 h_flex()
83 .justify_center()
84 .child(Label::new("Code at the speed of thought")),
85 )
86 .child(
87 v_flex()
88 .gap_2()
89 .child(
90 Button::new("choose-theme", "Choose a theme")
91 .full_width()
92 .on_click(cx.listener(|this, _, cx| {
93 this.telemetry.report_app_event(
94 "welcome page: change theme".to_string(),
95 );
96 this.workspace
97 .update(cx, |workspace, cx| {
98 theme_selector::toggle(
99 workspace,
100 &Default::default(),
101 cx,
102 )
103 })
104 .ok();
105 })),
106 )
107 .child(
108 Button::new("choose-keymap", "Choose a keymap")
109 .full_width()
110 .on_click(cx.listener(|this, _, cx| {
111 this.telemetry.report_app_event(
112 "welcome page: change keymap".to_string(),
113 );
114 this.workspace
115 .update(cx, |workspace, cx| {
116 base_keymap_picker::toggle(
117 workspace,
118 &Default::default(),
119 cx,
120 )
121 })
122 .ok();
123 })),
124 )
125 .child(
126 Button::new("install-cli", "Install the CLI")
127 .full_width()
128 .on_click(cx.listener(|this, _, cx| {
129 this.telemetry.report_app_event(
130 "welcome page: install cli".to_string(),
131 );
132 cx.app_mut()
133 .spawn(|cx| async move {
134 install_cli::install_cli(&cx).await
135 })
136 .detach_and_log_err(cx);
137 })),
138 )
139 .child(
140 Button::new("sign-in-to-copilot", "Sign in to GitHub Copilot")
141 .full_width()
142 .on_click(cx.listener(|this, _, cx| {
143 this.telemetry.report_app_event(
144 "welcome page: sign in to copilot".to_string(),
145 );
146 copilot_ui::initiate_sign_in(cx);
147 })),
148 ),
149 )
150 .child(
151 v_flex()
152 .p_3()
153 .gap_2()
154 .bg(cx.theme().colors().elevated_surface_background)
155 .border_1()
156 .border_color(cx.theme().colors().border)
157 .rounded_md()
158 .child(CheckboxWithLabel::new(
159 "enable-vim",
160 Label::new("Enable vim mode"),
161 if VimModeSetting::get_global(cx).0 {
162 ui::Selection::Selected
163 } else {
164 ui::Selection::Unselected
165 },
166 cx.listener(move |this, selection, cx| {
167 this.telemetry
168 .report_app_event("welcome page: toggle vim".to_string());
169 this.update_settings::<VimModeSetting>(
170 selection,
171 cx,
172 |setting, value| *setting = Some(value),
173 );
174 }),
175 ))
176 .child(CheckboxWithLabel::new(
177 "enable-telemetry",
178 Label::new("Send anonymous usage data"),
179 if TelemetrySettings::get_global(cx).metrics {
180 ui::Selection::Selected
181 } else {
182 ui::Selection::Unselected
183 },
184 cx.listener(move |this, selection, cx| {
185 this.telemetry.report_app_event(
186 "welcome page: toggle metric telemetry".to_string(),
187 );
188 this.update_settings::<TelemetrySettings>(selection, cx, {
189 let telemetry = this.telemetry.clone();
190
191 move |settings, value| {
192 settings.metrics = Some(value);
193
194 telemetry.report_setting_event(
195 "metric telemetry",
196 value.to_string(),
197 );
198 }
199 });
200 }),
201 ))
202 .child(CheckboxWithLabel::new(
203 "enable-crash",
204 Label::new("Send crash reports"),
205 if TelemetrySettings::get_global(cx).diagnostics {
206 ui::Selection::Selected
207 } else {
208 ui::Selection::Unselected
209 },
210 cx.listener(move |this, selection, cx| {
211 this.telemetry.report_app_event(
212 "welcome page: toggle diagnostic telemetry".to_string(),
213 );
214 this.update_settings::<TelemetrySettings>(selection, cx, {
215 let telemetry = this.telemetry.clone();
216
217 move |settings, value| {
218 settings.diagnostics = Some(value);
219
220 telemetry.report_setting_event(
221 "diagnostic telemetry",
222 value.to_string(),
223 );
224 }
225 });
226 }),
227 )),
228 ),
229 )
230 }
231}
232
233impl WelcomePage {
234 pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
235 let this = cx.new_view(|cx| {
236 cx.on_release(|this: &mut Self, _, _| {
237 this.telemetry
238 .report_app_event("welcome page: close".to_string());
239 })
240 .detach();
241
242 WelcomePage {
243 focus_handle: cx.focus_handle(),
244 workspace: workspace.weak_handle(),
245 telemetry: workspace.client().telemetry().clone(),
246 _settings_subscription: cx
247 .observe_global::<SettingsStore>(move |_, cx| cx.notify()),
248 }
249 });
250
251 this
252 }
253
254 fn update_settings<T: Settings>(
255 &mut self,
256 selection: &Selection,
257 cx: &mut ViewContext<Self>,
258 callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
259 ) {
260 if let Some(workspace) = self.workspace.upgrade() {
261 let fs = workspace.read(cx).app_state().fs.clone();
262 let selection = *selection;
263 settings::update_settings_file::<T>(fs, cx, move |settings| {
264 let value = match selection {
265 Selection::Unselected => false,
266 Selection::Selected => true,
267 _ => return,
268 };
269
270 callback(settings, value)
271 });
272 }
273 }
274}
275
276impl EventEmitter<ItemEvent> for WelcomePage {}
277
278impl FocusableView for WelcomePage {
279 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
280 self.focus_handle.clone()
281 }
282}
283
284impl Item for WelcomePage {
285 type Event = ItemEvent;
286
287 fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
288 Label::new("Welcome to Zed!")
289 .color(if params.selected {
290 Color::Default
291 } else {
292 Color::Muted
293 })
294 .into_any_element()
295 }
296
297 fn telemetry_event_text(&self) -> Option<&'static str> {
298 Some("welcome page")
299 }
300
301 fn show_toolbar(&self) -> bool {
302 false
303 }
304
305 fn clone_on_split(
306 &self,
307 _workspace_id: WorkspaceId,
308 cx: &mut ViewContext<Self>,
309 ) -> Option<View<Self>> {
310 Some(cx.new_view(|cx| WelcomePage {
311 focus_handle: cx.focus_handle(),
312 workspace: self.workspace.clone(),
313 telemetry: self.telemetry.clone(),
314 _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
315 }))
316 }
317
318 fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
319 f(*event)
320 }
321}