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