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