1// Disable command line from opening on release mode
2#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
4mod logger;
5mod reliability;
6mod zed;
7
8use anyhow::{anyhow, Context as _, Result};
9use clap::{command, Parser};
10use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
11use client::{parse_zed_link, Client, ProxySettings, UserStore};
12use collab_ui::channel_view::ChannelView;
13use collections::HashMap;
14use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
15use editor::Editor;
16use extension::ExtensionHostProxy;
17use extension_host::ExtensionStore;
18use fs::{Fs, RealFs};
19use futures::{future, StreamExt};
20use git::GitHostingProviderRegistry;
21use gpui::{App, AppContext as _, Application, AsyncApp, UpdateGlobal as _};
22
23use gpui_tokio::Tokio;
24use http_client::{read_proxy_from_env, Uri};
25use language::LanguageRegistry;
26use prompt_store::PromptBuilder;
27use reqwest_client::ReqwestClient;
28
29use assets::Assets;
30use logger::{init_logger, init_stdout_logger};
31use node_runtime::{NodeBinaryOptions, NodeRuntime};
32use parking_lot::Mutex;
33use project::project_settings::ProjectSettings;
34use recent_projects::{open_ssh_project, SshSettings};
35use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
36use session::{AppSession, Session};
37use settings::{watch_config_file, Settings, SettingsStore};
38use std::{
39 env,
40 io::{self, IsTerminal},
41 path::{Path, PathBuf},
42 process,
43 sync::Arc,
44};
45use theme::{
46 ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
47 ThemeSettings,
48};
49use util::{maybe, ResultExt, TryFutureExt};
50use uuid::Uuid;
51use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
52use workspace::{AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore};
53use zed::{
54 app_menus, build_window_options, derive_paths_with_position, handle_cli_connection,
55 handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes,
56 initialize_workspace, inline_completion_registry, open_paths_with_positions, OpenListener,
57 OpenRequest,
58};
59
60#[cfg(unix)]
61use util::{load_login_shell_environment, load_shell_from_passwd};
62
63#[cfg(feature = "mimalloc")]
64#[global_allocator]
65static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
66
67fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
68 let message = "Zed failed to launch";
69 let error_details = errors
70 .into_iter()
71 .flat_map(|(kind, paths)| {
72 #[allow(unused_mut)] // for non-unix platforms
73 let mut error_kind_details = match paths.len() {
74 0 => return None,
75 1 => format!(
76 "{kind} when creating directory {:?}",
77 paths.first().expect("match arm checks for a single entry")
78 ),
79 _many => format!("{kind} when creating directories {paths:?}"),
80 };
81
82 #[cfg(unix)]
83 {
84 match kind {
85 io::ErrorKind::PermissionDenied => {
86 error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\
87 \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`");
88 }
89 _ => {}
90 }
91 }
92
93 Some(error_kind_details)
94 })
95 .collect::<Vec<_>>().join("\n\n");
96
97 eprintln!("{message}: {error_details}");
98 Application::new().run(move |cx| {
99 if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
100 cx.new(|_| gpui::Empty)
101 }) {
102 window
103 .update(cx, |_, window, cx| {
104 let response = window.prompt(
105 gpui::PromptLevel::Critical,
106 message,
107 Some(&error_details),
108 &["Exit"],
109 cx,
110 );
111
112 cx.spawn_in(window, async move |_, cx| {
113 response.await?;
114 cx.update(|_, cx| cx.quit())
115 })
116 .detach_and_log_err(cx);
117 })
118 .log_err();
119 } else {
120 fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx)
121 }
122 })
123}
124
125fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {
126 cx.update(|cx| fail_to_open_window(e, cx)).log_err();
127}
128
129fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
130 eprintln!(
131 "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
132 );
133 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
134 {
135 process::exit(1);
136 }
137
138 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
139 {
140 use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
141 _cx.spawn(async move |_cx| {
142 let Ok(proxy) = NotificationProxy::new().await else {
143 process::exit(1);
144 };
145
146 let notification_id = "dev.zed.Oops";
147 proxy
148 .add_notification(
149 notification_id,
150 Notification::new("Zed failed to launch")
151 .body(Some(
152 format!(
153 "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
154 )
155 .as_str(),
156 ))
157 .priority(Priority::High)
158 .icon(ashpd::desktop::Icon::with_names(&[
159 "dialog-question-symbolic",
160 ])),
161 )
162 .await
163 .ok();
164
165 process::exit(1);
166 })
167 .detach();
168 }
169}
170
171fn main() {
172 let args = Args::parse();
173
174 #[cfg(all(not(debug_assertions), target_os = "windows"))]
175 unsafe {
176 use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
177
178 if args.foreground {
179 let _ = AttachConsole(ATTACH_PARENT_PROCESS);
180 }
181 }
182
183 menu::init();
184 zed_actions::init();
185
186 let file_errors = init_paths();
187 if !file_errors.is_empty() {
188 files_not_created_on_launch(file_errors);
189 return;
190 }
191
192 if stdout_is_a_pty() {
193 init_stdout_logger();
194 } else {
195 init_logger();
196 }
197
198 log::info!("========== starting zed ==========");
199
200 let app = Application::new().with_assets(Assets);
201
202 let system_id = app.background_executor().block(system_id()).ok();
203 let installation_id = app.background_executor().block(installation_id()).ok();
204 let session_id = Uuid::new_v4().to_string();
205 let session = app.background_executor().block(Session::new());
206 let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
207 let app_commit_sha =
208 option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha(commit_sha.to_string()));
209
210 reliability::init_panic_hook(
211 app_version,
212 app_commit_sha.clone(),
213 system_id.as_ref().map(|id| id.to_string()),
214 installation_id.as_ref().map(|id| id.to_string()),
215 session_id.clone(),
216 );
217
218 if args.system_specs {
219 let system_specs = feedback::system_specs::SystemSpecs::new_stateless(
220 app_version,
221 app_commit_sha.clone(),
222 *release_channel::RELEASE_CHANNEL,
223 );
224 println!("Zed System Specs (from CLI):\n{}", system_specs);
225 return;
226 }
227
228 let (open_listener, mut open_rx) = OpenListener::new();
229
230 let failed_single_instance_check = if *db::ZED_STATELESS
231 || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
232 {
233 false
234 } else {
235 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
236 {
237 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
238 }
239
240 #[cfg(target_os = "windows")]
241 {
242 !crate::zed::windows_only_instance::check_single_instance(open_listener.clone(), &args)
243 }
244
245 #[cfg(target_os = "macos")]
246 {
247 use zed::mac_only_instance::*;
248 ensure_only_instance() != IsOnlyInstance::Yes
249 }
250 };
251 if failed_single_instance_check {
252 println!("zed is already running");
253 return;
254 }
255
256 let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
257 let git_binary_path =
258 if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
259 app.path_for_auxiliary_executable("git")
260 .context("could not find git binary path")
261 .log_err()
262 } else {
263 None
264 };
265 log::info!("Using git binary path: {:?}", git_binary_path);
266
267 let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
268 let user_settings_file_rx = watch_config_file(
269 &app.background_executor(),
270 fs.clone(),
271 paths::settings_file().clone(),
272 );
273 let user_keymap_file_rx = watch_config_file(
274 &app.background_executor(),
275 fs.clone(),
276 paths::keymap_file().clone(),
277 );
278
279 #[cfg(unix)]
280 if !stdout_is_a_pty() {
281 app.background_executor()
282 .spawn(async {
283 load_shell_from_passwd().log_err();
284 load_login_shell_environment().log_err();
285 })
286 .detach()
287 };
288
289 app.on_open_urls({
290 let open_listener = open_listener.clone();
291 move |urls| open_listener.open_urls(urls)
292 });
293 app.on_reopen(move |cx| {
294 if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
295 {
296 cx.spawn({
297 let app_state = app_state.clone();
298 async move |mut cx| {
299 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
300 fail_to_open_window_async(e, &mut cx)
301 }
302 }
303 })
304 .detach();
305 }
306 });
307
308 app.run(move |cx| {
309 release_channel::init(app_version, cx);
310 gpui_tokio::init(cx);
311 if let Some(app_commit_sha) = app_commit_sha {
312 AppCommitSha::set_global(app_commit_sha, cx);
313 }
314 settings::init(cx);
315 zlog_settings::init(cx);
316 handle_settings_file_changes(user_settings_file_rx, cx, handle_settings_changed);
317 handle_keymap_file_changes(user_keymap_file_rx, cx);
318 client::init_settings(cx);
319 let user_agent = format!(
320 "Zed/{} ({}; {})",
321 AppVersion::global(cx),
322 std::env::consts::OS,
323 std::env::consts::ARCH
324 );
325 let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();
326 let proxy_url = proxy_str
327 .as_ref()
328 .and_then(|input| {
329 input
330 .parse::<Uri>()
331 .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
332 .ok()
333 })
334 .or_else(read_proxy_from_env);
335 let http = {
336 let _guard = Tokio::handle(cx).enter();
337
338 ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
339 .expect("could not start HTTP client")
340 };
341 cx.set_http_client(Arc::new(http));
342
343 <dyn Fs>::set_global(fs.clone(), cx);
344
345 GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
346 git_hosting_providers::init(cx);
347
348 OpenListener::set_global(cx, open_listener.clone());
349
350 extension::init(cx);
351 let extension_host_proxy = ExtensionHostProxy::global(cx);
352
353 let client = Client::production(cx);
354 cx.set_http_client(client.http_client().clone());
355 let mut languages = LanguageRegistry::new(cx.background_executor().clone());
356 languages.set_language_server_download_dir(paths::languages_dir().clone());
357 let languages = Arc::new(languages);
358 let (tx, rx) = async_watch::channel(None);
359 cx.observe_global::<SettingsStore>(move |cx| {
360 let settings = &ProjectSettings::get_global(cx).node;
361 let options = NodeBinaryOptions {
362 allow_path_lookup: !settings.ignore_system_version.unwrap_or_default(),
363 // TODO: Expose this setting
364 allow_binary_download: true,
365 use_paths: settings.path.as_ref().map(|node_path| {
366 let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
367 let npm_path = settings
368 .npm_path
369 .as_ref()
370 .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
371 (
372 node_path.clone(),
373 npm_path.unwrap_or_else(|| {
374 let base_path = PathBuf::new();
375 node_path.parent().unwrap_or(&base_path).join("npm")
376 }),
377 )
378 }),
379 };
380 tx.send(Some(options)).log_err();
381 })
382 .detach();
383 let node_runtime = NodeRuntime::new(client.http_client(), rx);
384
385 language::init(cx);
386 language_extension::init(extension_host_proxy.clone(), languages.clone());
387 languages::init(languages.clone(), node_runtime.clone(), cx);
388 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
389 let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
390
391 Client::set_global(client.clone(), cx);
392
393 zed::init(cx);
394 project::Project::init(&client, cx);
395 debugger_ui::init(cx);
396 debugger_tools::init(cx);
397 client::init(&client, cx);
398 let telemetry = client.telemetry();
399 telemetry.start(
400 system_id.as_ref().map(|id| id.to_string()),
401 installation_id.as_ref().map(|id| id.to_string()),
402 session_id.clone(),
403 cx,
404 );
405
406 // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
407 if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
408 match (&system_id, &installation_id) {
409 (IdType::New(_), IdType::New(_)) => {
410 telemetry::event!("App First Opened");
411 telemetry::event!("App First Opened For Release Channel");
412 }
413 (IdType::Existing(_), IdType::New(_)) => {
414 telemetry::event!("App First Opened For Release Channel");
415 }
416 (_, IdType::Existing(_)) => {
417 telemetry::event!("App Opened");
418 }
419 }
420 }
421 let app_session = cx.new(|cx| AppSession::new(session, cx));
422
423 let app_state = Arc::new(AppState {
424 languages: languages.clone(),
425 client: client.clone(),
426 user_store: user_store.clone(),
427 fs: fs.clone(),
428 build_window_options,
429 workspace_store,
430 node_runtime: node_runtime.clone(),
431 session: app_session,
432 });
433 AppState::set_global(Arc::downgrade(&app_state), cx);
434
435 auto_update::init(client.http_client(), cx);
436 auto_update_ui::init(cx);
437 reliability::init(
438 client.http_client(),
439 system_id.as_ref().map(|id| id.to_string()),
440 installation_id.clone().map(|id| id.to_string()),
441 session_id.clone(),
442 cx,
443 );
444
445 SystemAppearance::init(cx);
446 theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
447 theme_extension::init(
448 extension_host_proxy.clone(),
449 ThemeRegistry::global(cx),
450 cx.background_executor().clone(),
451 );
452 command_palette::init(cx);
453 let copilot_language_server_id = app_state.languages.next_language_server_id();
454 copilot::init(
455 copilot_language_server_id,
456 app_state.fs.clone(),
457 app_state.client.http_client(),
458 app_state.node_runtime.clone(),
459 cx,
460 );
461 supermaven::init(app_state.client.clone(), cx);
462 language_model::init(app_state.client.clone(), cx);
463 language_models::init(
464 app_state.user_store.clone(),
465 app_state.client.clone(),
466 app_state.fs.clone(),
467 cx,
468 );
469 snippet_provider::init(cx);
470 inline_completion_registry::init(
471 app_state.client.clone(),
472 app_state.user_store.clone(),
473 cx,
474 );
475 let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
476 assistant::init(
477 app_state.fs.clone(),
478 app_state.client.clone(),
479 prompt_builder.clone(),
480 cx,
481 );
482 assistant2::init(
483 app_state.fs.clone(),
484 app_state.client.clone(),
485 prompt_builder.clone(),
486 cx,
487 );
488 assistant_tools::init(app_state.client.http_client(), cx);
489 repl::init(app_state.fs.clone(), cx);
490 extension_host::init(
491 extension_host_proxy,
492 app_state.fs.clone(),
493 app_state.client.clone(),
494 app_state.node_runtime.clone(),
495 cx,
496 );
497 recent_projects::init(cx);
498
499 load_embedded_fonts(cx);
500
501 app_state.languages.set_theme(cx.theme().clone());
502 editor::init(cx);
503 image_viewer::init(cx);
504 repl::notebook::init(cx);
505 diagnostics::init(cx);
506
507 audio::init(Assets, cx);
508 workspace::init(app_state.clone(), cx);
509 ui_prompt::init(cx);
510
511 go_to_line::init(cx);
512 file_finder::init(cx);
513 tab_switcher::init(cx);
514 outline::init(cx);
515 project_symbols::init(cx);
516 project_panel::init(cx);
517 outline_panel::init(cx);
518 tasks_ui::init(cx);
519 snippets_ui::init(cx);
520 channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
521 search::init(cx);
522 vim::init(cx);
523 terminal_view::init(cx);
524 journal::init(app_state.clone(), cx);
525 language_selector::init(cx);
526 toolchain_selector::init(cx);
527 theme_selector::init(cx);
528 language_tools::init(cx);
529 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
530 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
531 collab_ui::init(&app_state, cx);
532 git_ui::init(cx);
533 feedback::init(cx);
534 markdown_preview::init(cx);
535 welcome::init(cx);
536 settings_ui::init(cx);
537 extensions_ui::init(cx);
538 zeta::init(cx);
539
540 cx.observe_global::<SettingsStore>({
541 let fs = fs.clone();
542 let languages = app_state.languages.clone();
543 let http = app_state.client.http_client();
544 let client = app_state.client.clone();
545 move |cx| {
546 for &mut window in cx.windows().iter_mut() {
547 let background_appearance = cx.theme().window_background_appearance();
548 window
549 .update(cx, |_, window, _| {
550 window.set_background_appearance(background_appearance)
551 })
552 .ok();
553 }
554
555 eager_load_active_theme_and_icon_theme(fs.clone(), cx);
556
557 languages.set_theme(cx.theme().clone());
558 let new_host = &client::ClientSettings::get_global(cx).server_url;
559 if &http.base_url() != new_host {
560 http.set_base_url(new_host);
561 if client.status().borrow().is_connected() {
562 client.reconnect(&cx.to_async());
563 }
564 }
565 }
566 })
567 .detach();
568 telemetry::event!(
569 "Settings Changed",
570 setting = "theme",
571 value = cx.theme().name.to_string()
572 );
573 telemetry::event!(
574 "Settings Changed",
575 setting = "keymap",
576 value = BaseKeymap::get_global(cx).to_string()
577 );
578 telemetry.flush_events();
579
580 let fs = app_state.fs.clone();
581 load_user_themes_in_background(fs.clone(), cx);
582 watch_themes(fs.clone(), cx);
583 watch_languages(fs.clone(), app_state.languages.clone(), cx);
584
585 cx.set_menus(app_menus());
586 initialize_workspace(app_state.clone(), prompt_builder, cx);
587
588 cx.activate(true);
589
590 cx.spawn({
591 let client = app_state.client.clone();
592 async move |cx| authenticate(client, &cx).await
593 })
594 .detach_and_log_err(cx);
595
596 let urls: Vec<_> = args
597 .paths_or_urls
598 .iter()
599 .filter_map(|arg| parse_url_arg(arg, cx).log_err())
600 .collect();
601
602 if !urls.is_empty() {
603 open_listener.open_urls(urls)
604 }
605
606 match open_rx
607 .try_next()
608 .ok()
609 .flatten()
610 .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
611 {
612 Some(request) => {
613 handle_open_request(request, app_state.clone(), cx);
614 }
615 None => {
616 cx.spawn({
617 let app_state = app_state.clone();
618 async move |mut cx| {
619 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
620 fail_to_open_window_async(e, &mut cx)
621 }
622 }
623 })
624 .detach();
625 }
626 }
627
628 let app_state = app_state.clone();
629
630 component_preview::init(app_state.clone(), cx);
631
632 cx.spawn(async move |cx| {
633 while let Some(urls) = open_rx.next().await {
634 cx.update(|cx| {
635 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
636 handle_open_request(request, app_state.clone(), cx);
637 }
638 })
639 .ok();
640 }
641 })
642 .detach();
643 });
644}
645
646fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
647 if let Some(connection) = request.cli_connection {
648 let app_state = app_state.clone();
649 cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
650 .detach();
651 return;
652 }
653
654 if let Some(action_index) = request.dock_menu_action {
655 cx.perform_dock_menu_action(action_index);
656 return;
657 }
658
659 if let Some(connection_options) = request.ssh_connection {
660 cx.spawn(async move |mut cx| {
661 let paths_with_position =
662 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
663 open_ssh_project(
664 connection_options,
665 paths_with_position.into_iter().map(|p| p.path).collect(),
666 app_state,
667 workspace::OpenOptions::default(),
668 &mut cx,
669 )
670 .await
671 })
672 .detach_and_log_err(cx);
673 return;
674 }
675
676 let mut task = None;
677 if !request.open_paths.is_empty() {
678 let app_state = app_state.clone();
679 task = Some(cx.spawn(async move |mut cx| {
680 let paths_with_position =
681 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
682 let (_window, results) = open_paths_with_positions(
683 &paths_with_position,
684 app_state,
685 workspace::OpenOptions::default(),
686 &mut cx,
687 )
688 .await?;
689 for result in results.into_iter().flatten() {
690 if let Err(err) = result {
691 log::error!("Error opening path: {err}",);
692 }
693 }
694 anyhow::Ok(())
695 }));
696 }
697
698 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
699 cx.spawn(async move |mut cx| {
700 let result = maybe!(async {
701 if let Some(task) = task {
702 task.await?;
703 }
704 let client = app_state.client.clone();
705 // we continue even if authentication fails as join_channel/ open channel notes will
706 // show a visible error message.
707 authenticate(client, &cx).await.log_err();
708
709 if let Some(channel_id) = request.join_channel {
710 cx.update(|cx| {
711 workspace::join_channel(
712 client::ChannelId(channel_id),
713 app_state.clone(),
714 None,
715 cx,
716 )
717 })?
718 .await?;
719 }
720
721 let workspace_window =
722 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
723 let workspace = workspace_window.entity(cx)?;
724
725 let mut promises = Vec::new();
726 for (channel_id, heading) in request.open_channel_notes {
727 promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
728 ChannelView::open(
729 client::ChannelId(channel_id),
730 heading,
731 workspace.clone(),
732 window,
733 cx,
734 )
735 .log_err()
736 })?)
737 }
738 future::join_all(promises).await;
739 anyhow::Ok(())
740 })
741 .await;
742 if let Err(err) = result {
743 fail_to_open_window_async(err, &mut cx);
744 }
745 })
746 .detach()
747 } else if let Some(task) = task {
748 cx.spawn(async move |mut cx| {
749 if let Err(err) = task.await {
750 fail_to_open_window_async(err, &mut cx);
751 }
752 })
753 .detach();
754 }
755}
756
757async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> Result<()> {
758 if stdout_is_a_pty() {
759 if client::IMPERSONATE_LOGIN.is_some() {
760 client.authenticate_and_connect(false, cx).await?;
761 } else if client.has_credentials(cx).await {
762 client.authenticate_and_connect(true, cx).await?;
763 }
764 } else if client.has_credentials(cx).await {
765 client.authenticate_and_connect(true, cx).await?;
766 }
767 Ok::<_, anyhow::Error>(())
768}
769
770async fn system_id() -> Result<IdType> {
771 let key_name = "system_id".to_string();
772
773 if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
774 return Ok(IdType::Existing(system_id));
775 }
776
777 let system_id = Uuid::new_v4().to_string();
778
779 GLOBAL_KEY_VALUE_STORE
780 .write_kvp(key_name, system_id.clone())
781 .await?;
782
783 Ok(IdType::New(system_id))
784}
785
786async fn installation_id() -> Result<IdType> {
787 let legacy_key_name = "device_id".to_string();
788 let key_name = "installation_id".to_string();
789
790 // Migrate legacy key to new key
791 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
792 KEY_VALUE_STORE
793 .write_kvp(key_name, installation_id.clone())
794 .await?;
795 KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
796 return Ok(IdType::Existing(installation_id));
797 }
798
799 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
800 return Ok(IdType::Existing(installation_id));
801 }
802
803 let installation_id = Uuid::new_v4().to_string();
804
805 KEY_VALUE_STORE
806 .write_kvp(key_name, installation_id.clone())
807 .await?;
808
809 Ok(IdType::New(installation_id))
810}
811
812async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
813 if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
814 for location in locations {
815 match location {
816 SerializedWorkspaceLocation::Local(location, _) => {
817 let task = cx.update(|cx| {
818 workspace::open_paths(
819 location.paths().as_ref(),
820 app_state.clone(),
821 workspace::OpenOptions::default(),
822 cx,
823 )
824 })?;
825 task.await?;
826 }
827 SerializedWorkspaceLocation::Ssh(ssh) => {
828 let connection_options = cx.update(|cx| {
829 SshSettings::get_global(cx)
830 .connection_options_for(ssh.host, ssh.port, ssh.user)
831 })?;
832 let app_state = app_state.clone();
833 cx.spawn(async move |cx| {
834 recent_projects::open_ssh_project(
835 connection_options,
836 ssh.paths.into_iter().map(PathBuf::from).collect(),
837 app_state,
838 workspace::OpenOptions::default(),
839 cx,
840 )
841 .await
842 .log_err();
843 })
844 .detach();
845 }
846 }
847 }
848 } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
849 cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
850 } else {
851 cx.update(|cx| {
852 workspace::open_new(
853 Default::default(),
854 app_state,
855 cx,
856 |workspace, window, cx| {
857 Editor::new_file(workspace, &Default::default(), window, cx)
858 },
859 )
860 })?
861 .await?;
862 }
863
864 Ok(())
865}
866
867pub(crate) async fn restorable_workspace_locations(
868 cx: &mut AsyncApp,
869 app_state: &Arc<AppState>,
870) -> Option<Vec<SerializedWorkspaceLocation>> {
871 let mut restore_behavior = cx
872 .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
873 .ok()?;
874
875 let session_handle = app_state.session.clone();
876 let (last_session_id, last_session_window_stack) = cx
877 .update(|cx| {
878 let session = session_handle.read(cx);
879
880 (
881 session.last_session_id().map(|id| id.to_string()),
882 session.last_session_window_stack(),
883 )
884 })
885 .ok()?;
886
887 if last_session_id.is_none()
888 && matches!(
889 restore_behavior,
890 workspace::RestoreOnStartupBehavior::LastSession
891 )
892 {
893 restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
894 }
895
896 match restore_behavior {
897 workspace::RestoreOnStartupBehavior::LastWorkspace => {
898 workspace::last_opened_workspace_location()
899 .await
900 .map(|location| vec![location])
901 }
902 workspace::RestoreOnStartupBehavior::LastSession => {
903 if let Some(last_session_id) = last_session_id {
904 let ordered = last_session_window_stack.is_some();
905
906 let mut locations = workspace::last_session_workspace_locations(
907 &last_session_id,
908 last_session_window_stack,
909 )
910 .filter(|locations| !locations.is_empty());
911
912 // Since last_session_window_order returns the windows ordered front-to-back
913 // we need to open the window that was frontmost last.
914 if ordered {
915 if let Some(locations) = locations.as_mut() {
916 locations.reverse();
917 }
918 }
919
920 locations
921 } else {
922 None
923 }
924 }
925 _ => None,
926 }
927}
928
929fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
930 [
931 paths::config_dir(),
932 paths::extensions_dir(),
933 paths::languages_dir(),
934 paths::database_dir(),
935 paths::logs_dir(),
936 paths::temp_dir(),
937 ]
938 .into_iter()
939 .fold(HashMap::default(), |mut errors, path| {
940 if let Err(e) = std::fs::create_dir_all(path) {
941 errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
942 }
943 errors
944 })
945}
946
947fn stdout_is_a_pty() -> bool {
948 std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
949}
950
951#[derive(Parser, Debug)]
952#[command(name = "zed", disable_version_flag = true)]
953struct Args {
954 /// A sequence of space-separated paths or urls that you want to open.
955 ///
956 /// Use `path:line:row` syntax to open a file at a specific location.
957 /// Non-existing paths and directories will ignore `:line:row` suffix.
958 ///
959 /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
960 paths_or_urls: Vec<String>,
961
962 /// Instructs zed to run as a dev server on this machine. (not implemented)
963 #[arg(long)]
964 dev_server_token: Option<String>,
965
966 /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
967 /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
968 #[arg(long)]
969 system_specs: bool,
970
971 /// Run zed in the foreground, only used on Windows, to match the behavior of the behavior on macOS.
972 #[arg(long)]
973 #[cfg(target_os = "windows")]
974 #[arg(hide = true)]
975 foreground: bool,
976
977 /// The dock action to perform. This is used on Windows only.
978 #[arg(long)]
979 #[cfg(target_os = "windows")]
980 #[arg(hide = true)]
981 dock_action: Option<usize>,
982}
983
984#[derive(Clone, Debug)]
985enum IdType {
986 New(String),
987 Existing(String),
988}
989
990impl ToString for IdType {
991 fn to_string(&self) -> String {
992 match self {
993 IdType::New(id) | IdType::Existing(id) => id.clone(),
994 }
995 }
996}
997
998fn parse_url_arg(arg: &str, cx: &App) -> Result<String> {
999 match std::fs::canonicalize(Path::new(&arg)) {
1000 Ok(path) => Ok(format!("file://{}", path.display())),
1001 Err(error) => {
1002 if arg.starts_with("file://")
1003 || arg.starts_with("zed-cli://")
1004 || arg.starts_with("ssh://")
1005 || parse_zed_link(arg, cx).is_some()
1006 {
1007 Ok(arg.into())
1008 } else {
1009 Err(anyhow!("error parsing path argument: {}", error))
1010 }
1011 }
1012 }
1013}
1014
1015fn load_embedded_fonts(cx: &App) {
1016 let asset_source = cx.asset_source();
1017 let font_paths = asset_source.list("fonts").unwrap();
1018 let embedded_fonts = Mutex::new(Vec::new());
1019 let executor = cx.background_executor();
1020
1021 executor.block(executor.scoped(|scope| {
1022 for font_path in &font_paths {
1023 if !font_path.ends_with(".ttf") {
1024 continue;
1025 }
1026
1027 scope.spawn(async {
1028 let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1029 embedded_fonts.lock().push(font_bytes);
1030 });
1031 }
1032 }));
1033
1034 cx.text_system()
1035 .add_fonts(embedded_fonts.into_inner())
1036 .unwrap();
1037}
1038
1039/// Eagerly loads the active theme and icon theme based on the selections in the
1040/// theme settings.
1041///
1042/// This fast path exists to load these themes as soon as possible so the user
1043/// doesn't see the default themes while waiting on extensions to load.
1044fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
1045 let extension_store = ExtensionStore::global(cx);
1046 let theme_registry = ThemeRegistry::global(cx);
1047 let theme_settings = ThemeSettings::get_global(cx);
1048 let appearance = SystemAppearance::global(cx).0;
1049
1050 if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
1051 let theme_name = theme_selection.theme(appearance);
1052 if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) {
1053 if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) {
1054 cx.spawn({
1055 let theme_registry = theme_registry.clone();
1056 let fs = fs.clone();
1057 async move |cx| {
1058 theme_registry.load_user_theme(&theme_path, fs).await?;
1059
1060 cx.update(|cx| {
1061 ThemeSettings::reload_current_theme(cx);
1062 })
1063 }
1064 })
1065 .detach_and_log_err(cx);
1066 }
1067 }
1068 }
1069
1070 if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
1071 let icon_theme_name = icon_theme_selection.icon_theme(appearance);
1072 if matches!(
1073 theme_registry.get_icon_theme(icon_theme_name),
1074 Err(IconThemeNotFoundError(_))
1075 ) {
1076 if let Some((icon_theme_path, icons_root_path)) = extension_store
1077 .read(cx)
1078 .path_to_extension_icon_theme(icon_theme_name)
1079 {
1080 cx.spawn({
1081 let theme_registry = theme_registry.clone();
1082 let fs = fs.clone();
1083 async move |cx| {
1084 theme_registry
1085 .load_icon_theme(&icon_theme_path, &icons_root_path, fs)
1086 .await?;
1087
1088 cx.update(|cx| {
1089 ThemeSettings::reload_current_icon_theme(cx);
1090 })
1091 }
1092 })
1093 .detach_and_log_err(cx);
1094 }
1095 }
1096 }
1097}
1098
1099/// Spawns a background task to load the user themes from the themes directory.
1100fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1101 cx.spawn({
1102 let fs = fs.clone();
1103 async move |cx| {
1104 if let Some(theme_registry) =
1105 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1106 {
1107 let themes_dir = paths::themes_dir().as_ref();
1108 match fs
1109 .metadata(themes_dir)
1110 .await
1111 .ok()
1112 .flatten()
1113 .map(|m| m.is_dir)
1114 {
1115 Some(is_dir) => {
1116 anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1117 }
1118 None => {
1119 fs.create_dir(themes_dir).await.with_context(|| {
1120 format!("Failed to create themes dir at path {themes_dir:?}")
1121 })?;
1122 }
1123 }
1124 theme_registry.load_user_themes(themes_dir, fs).await?;
1125 cx.update(ThemeSettings::reload_current_theme)?;
1126 }
1127 anyhow::Ok(())
1128 }
1129 })
1130 .detach_and_log_err(cx);
1131}
1132
1133/// Spawns a background task to watch the themes directory for changes.
1134fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1135 use std::time::Duration;
1136 cx.spawn(async move |cx| {
1137 let (mut events, _) = fs
1138 .watch(paths::themes_dir(), Duration::from_millis(100))
1139 .await;
1140
1141 while let Some(paths) = events.next().await {
1142 for event in paths {
1143 if fs.metadata(&event.path).await.ok().flatten().is_some() {
1144 if let Some(theme_registry) =
1145 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1146 {
1147 if let Some(()) = theme_registry
1148 .load_user_theme(&event.path, fs.clone())
1149 .await
1150 .log_err()
1151 {
1152 cx.update(ThemeSettings::reload_current_theme).log_err();
1153 }
1154 }
1155 }
1156 }
1157 }
1158 })
1159 .detach()
1160}
1161
1162#[cfg(debug_assertions)]
1163fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1164 use std::time::Duration;
1165
1166 let path = {
1167 let p = Path::new("crates/languages/src");
1168 let Ok(full_path) = p.canonicalize() else {
1169 return;
1170 };
1171 full_path
1172 };
1173
1174 cx.spawn(async move |_| {
1175 let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1176 while let Some(event) = events.next().await {
1177 let has_language_file = event.iter().any(|event| {
1178 event
1179 .path
1180 .extension()
1181 .map(|ext| ext.to_string_lossy().as_ref() == "scm")
1182 .unwrap_or(false)
1183 });
1184 if has_language_file {
1185 languages.reload();
1186 }
1187 }
1188 })
1189 .detach()
1190}
1191
1192#[cfg(not(debug_assertions))]
1193fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}