1use client::{DisableAiSettings, TelemetrySettings, telemetry::Telemetry};
2use db::kvp::KEY_VALUE_STORE;
3use gpui::{
4 Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
5 ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, svg,
6};
7use language::language_settings::{EditPredictionProvider, all_language_settings};
8use settings::{Settings, SettingsStore};
9use std::sync::Arc;
10use ui::{CheckboxWithLabel, ElevationIndex, Tooltip, prelude::*};
11use util::ResultExt;
12use vim_mode_setting::VimModeSetting;
13use workspace::{
14 AppState, Welcome, Workspace, WorkspaceId,
15 dock::DockPosition,
16 item::{Item, ItemEvent},
17 open_new,
18};
19
20pub use multibuffer_hint::*;
21
22mod base_keymap_picker;
23mod multibuffer_hint;
24
25actions!(
26 welcome,
27 [
28 /// Resets the welcome screen hints to their initial state.
29 ResetHints
30 ]
31);
32
33pub const FIRST_OPEN: &str = "first_open";
34pub const DOCS_URL: &str = "https://zed.dev/docs/";
35
36pub fn init(cx: &mut App) {
37 cx.observe_new(|workspace: &mut Workspace, _, _cx| {
38 workspace.register_action(|workspace, _: &Welcome, window, cx| {
39 let welcome_page = WelcomePage::new(workspace, cx);
40 workspace.add_item_to_active_pane(Box::new(welcome_page), None, true, window, cx)
41 });
42 workspace
43 .register_action(|_workspace, _: &ResetHints, _, cx| MultibufferHint::set_count(0, cx));
44 })
45 .detach();
46
47 base_keymap_picker::init(cx);
48}
49
50pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {
51 open_new(
52 Default::default(),
53 app_state,
54 cx,
55 |workspace, window, cx| {
56 workspace.toggle_dock(DockPosition::Left, window, cx);
57 let welcome_page = WelcomePage::new(workspace, cx);
58 workspace.add_item_to_center(Box::new(welcome_page.clone()), window, cx);
59
60 window.focus(&welcome_page.focus_handle(cx));
61
62 cx.notify();
63
64 db::write_and_log(cx, || {
65 KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
66 });
67 },
68 )
69}
70
71pub struct WelcomePage {
72 workspace: WeakEntity<Workspace>,
73 focus_handle: FocusHandle,
74 telemetry: Arc<Telemetry>,
75 _settings_subscription: Subscription,
76}
77
78impl Render for WelcomePage {
79 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
80 let edit_prediction_provider_is_zed =
81 all_language_settings(None, cx).edit_predictions.provider
82 == EditPredictionProvider::Zed;
83
84 let edit_prediction_label = if edit_prediction_provider_is_zed {
85 "Edit Prediction Enabled"
86 } else {
87 "Try Edit Prediction"
88 };
89
90 h_flex()
91 .size_full()
92 .bg(cx.theme().colors().editor_background)
93 .key_context("Welcome")
94 .track_focus(&self.focus_handle(cx))
95 .child(
96 v_flex()
97 .gap_8()
98 .mx_auto()
99 .child(
100 v_flex()
101 .w_full()
102 .child(
103 svg()
104 .path("icons/logo_96.svg")
105 .text_color(cx.theme().colors().icon_disabled)
106 .w(px(40.))
107 .h(px(40.))
108 .mx_auto()
109 .mb_4(),
110 )
111 .child(
112 h_flex()
113 .w_full()
114 .justify_center()
115 .child(Headline::new("Welcome to Zed")),
116 )
117 .child(
118 h_flex().w_full().justify_center().child(
119 Label::new("The editor for what's next")
120 .color(Color::Muted)
121 .italic(),
122 ),
123 ),
124 )
125 .child(
126 h_flex()
127 .items_start()
128 .gap_8()
129 .child(
130 v_flex()
131 .gap_2()
132 .pr_8()
133 .border_r_1()
134 .border_color(cx.theme().colors().border_variant)
135 .child(
136 self.section_label( cx).child(
137 Label::new("Get Started")
138 .size(LabelSize::XSmall)
139 .color(Color::Muted),
140 ),
141 )
142 .child(
143 Button::new("choose-theme", "Choose a Theme")
144 .icon(IconName::SwatchBook)
145 .icon_size(IconSize::XSmall)
146 .icon_color(Color::Muted)
147 .icon_position(IconPosition::Start)
148 .on_click(cx.listener(|this, _, window, cx| {
149 telemetry::event!("Welcome Theme Changed");
150 this.workspace
151 .update(cx, |_workspace, cx| {
152 window.dispatch_action(zed_actions::theme_selector::Toggle::default().boxed_clone(), cx);
153 })
154 .ok();
155 })),
156 )
157 .child(
158 Button::new("choose-keymap", "Choose a Keymap")
159 .icon(IconName::Keyboard)
160 .icon_size(IconSize::XSmall)
161 .icon_color(Color::Muted)
162 .icon_position(IconPosition::Start)
163 .on_click(cx.listener(|this, _, window, cx| {
164 telemetry::event!("Welcome Keymap Changed");
165 this.workspace
166 .update(cx, |workspace, cx| {
167 base_keymap_picker::toggle(
168 workspace,
169 &Default::default(),
170 window, cx,
171 )
172 })
173 .ok();
174 })),
175 )
176 .when(!DisableAiSettings::get_global(cx).disable_ai, |parent| {
177 parent.child(
178 Button::new(
179 "edit_prediction_onboarding",
180 edit_prediction_label,
181 )
182 .disabled(edit_prediction_provider_is_zed)
183 .icon(IconName::ZedPredict)
184 .icon_size(IconSize::XSmall)
185 .icon_color(Color::Muted)
186 .icon_position(IconPosition::Start)
187 .on_click(
188 cx.listener(|_, _, window, cx| {
189 telemetry::event!("Welcome Screen Try Edit Prediction clicked");
190 window.dispatch_action(zed_actions::OpenZedPredictOnboarding.boxed_clone(), cx);
191 }),
192 ),
193 )
194 })
195 .child(
196 Button::new("edit settings", "Edit Settings")
197 .icon(IconName::Settings)
198 .icon_size(IconSize::XSmall)
199 .icon_color(Color::Muted)
200 .icon_position(IconPosition::Start)
201 .on_click(cx.listener(|_, _, window, cx| {
202 telemetry::event!("Welcome Settings Edited");
203 window.dispatch_action(Box::new(
204 zed_actions::OpenSettings,
205 ), cx);
206 })),
207 )
208
209 )
210 .child(
211 v_flex()
212 .gap_2()
213 .child(
214 self.section_label(cx).child(
215 Label::new("Resources")
216 .size(LabelSize::XSmall)
217 .color(Color::Muted),
218 ),
219 )
220 .when(cfg!(target_os = "macos"), |el| {
221 el.child(
222 Button::new("install-cli", "Install the CLI")
223 .icon(IconName::Terminal)
224 .icon_size(IconSize::XSmall)
225 .icon_color(Color::Muted)
226 .icon_position(IconPosition::Start)
227 .on_click(cx.listener(|this, _, window, cx| {
228 telemetry::event!("Welcome CLI Installed");
229 this.workspace.update(cx, |_, cx|{
230 install_cli::install_cli(window, cx);
231 }).log_err();
232 })),
233 )
234 })
235 .child(
236 Button::new("view-docs", "View Documentation")
237 .icon(IconName::FileCode)
238 .icon_size(IconSize::XSmall)
239 .icon_color(Color::Muted)
240 .icon_position(IconPosition::Start)
241 .on_click(cx.listener(|_, _, _, cx| {
242 telemetry::event!("Welcome Documentation Viewed");
243 cx.open_url(DOCS_URL);
244 })),
245 )
246 .child(
247 Button::new("explore-extensions", "Explore Extensions")
248 .icon(IconName::Blocks)
249 .icon_size(IconSize::XSmall)
250 .icon_color(Color::Muted)
251 .icon_position(IconPosition::Start)
252 .on_click(cx.listener(|_, _, window, cx| {
253 telemetry::event!("Welcome Extensions Page Opened");
254 window.dispatch_action(Box::new(
255 zed_actions::Extensions::default(),
256 ), cx);
257 })),
258 )
259 ),
260 )
261 .child(
262 v_container()
263 .px_2()
264 .gap_2()
265 .child(
266 h_flex()
267 .justify_between()
268 .child(
269 CheckboxWithLabel::new(
270 "enable-vim",
271 Label::new("Enable Vim Mode"),
272 if VimModeSetting::get_global(cx).0 {
273 ui::ToggleState::Selected
274 } else {
275 ui::ToggleState::Unselected
276 },
277 cx.listener(move |this, selection, _window, cx| {
278 telemetry::event!("Welcome Vim Mode Toggled");
279 this.update_settings::<VimModeSetting>(
280 selection,
281 cx,
282 |setting, value| *setting = Some(value),
283 );
284 }),
285 )
286 .fill()
287 .elevation(ElevationIndex::ElevatedSurface),
288 )
289 .child(
290 IconButton::new("vim-mode", IconName::Info)
291 .icon_size(IconSize::XSmall)
292 .icon_color(Color::Muted)
293 .tooltip(
294 Tooltip::text(
295 "You can also toggle Vim Mode via the command palette or Editor Controls menu.")
296 ),
297 ),
298 )
299 .child(
300 CheckboxWithLabel::new(
301 "enable-crash",
302 Label::new("Send Crash Reports"),
303 if TelemetrySettings::get_global(cx).diagnostics {
304 ui::ToggleState::Selected
305 } else {
306 ui::ToggleState::Unselected
307 },
308 cx.listener(move |this, selection, _window, cx| {
309 telemetry::event!("Welcome Diagnostic Telemetry Toggled");
310 this.update_settings::<TelemetrySettings>(selection, cx, {
311 move |settings, value| {
312 settings.diagnostics = Some(value);
313 telemetry::event!(
314 "Settings Changed",
315 setting = "diagnostic telemetry",
316 value
317 );
318 }
319 });
320 }),
321 )
322 .fill()
323 .elevation(ElevationIndex::ElevatedSurface),
324 )
325 .child(
326 CheckboxWithLabel::new(
327 "enable-telemetry",
328 Label::new("Send Telemetry"),
329 if TelemetrySettings::get_global(cx).metrics {
330 ui::ToggleState::Selected
331 } else {
332 ui::ToggleState::Unselected
333 },
334 cx.listener(move |this, selection, _window, cx| {
335 telemetry::event!("Welcome Metric Telemetry Toggled");
336 this.update_settings::<TelemetrySettings>(selection, cx, {
337 move |settings, value| {
338 settings.metrics = Some(value);
339 telemetry::event!(
340 "Settings Changed",
341 setting = "metric telemetry",
342 value
343 );
344 }
345 });
346 }),
347 )
348 .fill()
349 .elevation(ElevationIndex::ElevatedSurface),
350 ),
351 ),
352 )
353 }
354}
355
356impl WelcomePage {
357 pub fn new(workspace: &Workspace, cx: &mut Context<Workspace>) -> Entity<Self> {
358 let this = cx.new(|cx| {
359 cx.on_release(|_: &mut Self, _| {
360 telemetry::event!("Welcome Page Closed");
361 })
362 .detach();
363
364 WelcomePage {
365 focus_handle: cx.focus_handle(),
366 workspace: workspace.weak_handle(),
367 telemetry: workspace.client().telemetry().clone(),
368 _settings_subscription: cx
369 .observe_global::<SettingsStore>(move |_, cx| cx.notify()),
370 }
371 });
372
373 this
374 }
375
376 fn section_label(&self, cx: &mut App) -> Div {
377 div()
378 .pl_1()
379 .font_buffer(cx)
380 .text_color(Color::Muted.color(cx))
381 }
382
383 fn update_settings<T: Settings>(
384 &mut self,
385 selection: &ToggleState,
386 cx: &mut Context<Self>,
387 callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
388 ) {
389 if let Some(workspace) = self.workspace.upgrade() {
390 let fs = workspace.read(cx).app_state().fs.clone();
391 let selection = *selection;
392 settings::update_settings_file::<T>(fs, cx, move |settings, _| {
393 let value = match selection {
394 ToggleState::Unselected => false,
395 ToggleState::Selected => true,
396 _ => return,
397 };
398
399 callback(settings, value)
400 });
401 }
402 }
403}
404
405impl EventEmitter<ItemEvent> for WelcomePage {}
406
407impl Focusable for WelcomePage {
408 fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
409 self.focus_handle.clone()
410 }
411}
412
413impl Item for WelcomePage {
414 type Event = ItemEvent;
415
416 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
417 "Welcome".into()
418 }
419
420 fn telemetry_event_text(&self) -> Option<&'static str> {
421 Some("Welcome Page Opened")
422 }
423
424 fn show_toolbar(&self) -> bool {
425 false
426 }
427
428 fn clone_on_split(
429 &self,
430 _workspace_id: Option<WorkspaceId>,
431 _: &mut Window,
432 cx: &mut Context<Self>,
433 ) -> Option<Entity<Self>> {
434 Some(cx.new(|cx| WelcomePage {
435 focus_handle: cx.focus_handle(),
436 workspace: self.workspace.clone(),
437 telemetry: self.telemetry.clone(),
438 _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
439 }))
440 }
441
442 fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
443 f(*event)
444 }
445}