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