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