1mod reliability;
2mod zed;
3
4use agent_ui::AgentPanel;
5use anyhow::{Context as _, Error, Result};
6use clap::{Parser, command};
7use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
8use client::{Client, ProxySettings, UserStore, parse_zed_link};
9use collab_ui::channel_view::ChannelView;
10use collections::HashMap;
11use crashes::InitCrashHandler;
12use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
13use editor::Editor;
14use extension::ExtensionHostProxy;
15use extension_host::ExtensionStore;
16use fs::{Fs, RealFs};
17use futures::{StreamExt, channel::oneshot, future};
18use git::GitHostingProviderRegistry;
19use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _};
20
21use gpui_tokio::Tokio;
22use language::LanguageRegistry;
23use onboarding::{FIRST_OPEN, show_onboarding_view};
24use prompt_store::PromptBuilder;
25use remote::RemoteConnectionOptions;
26use reqwest_client::ReqwestClient;
27
28use assets::Assets;
29use node_runtime::{NodeBinaryOptions, NodeRuntime};
30use parking_lot::Mutex;
31use project::project_settings::ProjectSettings;
32use recent_projects::{SshSettings, open_remote_project};
33use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
34use session::{AppSession, Session};
35use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file};
36use std::{
37 env,
38 io::{self, IsTerminal},
39 path::{Path, PathBuf},
40 process,
41 sync::Arc,
42};
43use theme::{
44 ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
45 ThemeSettings,
46};
47use util::{ResultExt, TryFutureExt, maybe};
48use uuid::Uuid;
49use workspace::{
50 AppState, PathList, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceSettings,
51 WorkspaceStore, notifications::NotificationId,
52};
53use zed::{
54 OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
55 derive_paths_with_position, edit_prediction_registry, handle_cli_connection,
56 handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes,
57 initialize_workspace, open_paths_with_positions,
58};
59
60use crate::zed::OpenRequestKind;
61
62#[cfg(feature = "mimalloc")]
63#[global_allocator]
64static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
65
66fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
67 let message = "Zed failed to launch";
68 let error_details = errors
69 .into_iter()
70 .flat_map(|(kind, paths)| {
71 #[allow(unused_mut)] // for non-unix platforms
72 let mut error_kind_details = match paths.len() {
73 0 => return None,
74 1 => format!(
75 "{kind} when creating directory {:?}",
76 paths.first().expect("match arm checks for a single entry")
77 ),
78 _many => format!("{kind} when creating directories {paths:?}"),
79 };
80
81 #[cfg(unix)]
82 {
83 if kind == io::ErrorKind::PermissionDenied {
84 error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\
85 \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`");
86 }
87 }
88
89 Some(error_kind_details)
90 })
91 .collect::<Vec<_>>().join("\n\n");
92
93 eprintln!("{message}: {error_details}");
94 Application::new().run(move |cx| {
95 if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
96 cx.new(|_| gpui::Empty)
97 }) {
98 window
99 .update(cx, |_, window, cx| {
100 let response = window.prompt(
101 gpui::PromptLevel::Critical,
102 message,
103 Some(&error_details),
104 &["Exit"],
105 cx,
106 );
107
108 cx.spawn_in(window, async move |_, cx| {
109 response.await?;
110 cx.update(|_, cx| cx.quit())
111 })
112 .detach_and_log_err(cx);
113 })
114 .log_err();
115 } else {
116 fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx)
117 }
118 })
119}
120
121fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {
122 cx.update(|cx| fail_to_open_window(e, cx)).log_err();
123}
124
125fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
126 eprintln!(
127 "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
128 );
129 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
130 {
131 process::exit(1);
132 }
133
134 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
135 {
136 use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
137 _cx.spawn(async move |_cx| {
138 let Ok(proxy) = NotificationProxy::new().await else {
139 process::exit(1);
140 };
141
142 let notification_id = "dev.zed.Oops";
143 proxy
144 .add_notification(
145 notification_id,
146 Notification::new("Zed failed to launch")
147 .body(Some(
148 format!(
149 "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
150 )
151 .as_str(),
152 ))
153 .priority(Priority::High)
154 .icon(ashpd::desktop::Icon::with_names(&[
155 "dialog-question-symbolic",
156 ])),
157 )
158 .await
159 .ok();
160
161 process::exit(1);
162 })
163 .detach();
164 }
165}
166
167pub fn main() {
168 #[cfg(unix)]
169 util::prevent_root_execution();
170
171 let args = Args::parse();
172
173 // `zed --crash-handler` Makes zed operate in minidump crash handler mode
174 if let Some(socket) = &args.crash_handler {
175 crashes::crash_server(socket.as_path());
176 return;
177 }
178
179 // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
180 if let Some(socket) = &args.askpass {
181 askpass::main(socket);
182 return;
183 }
184
185 // `zed --nc` Makes zed operate in nc/netcat mode for use with MCP
186 if let Some(socket) = &args.nc {
187 match nc::main(socket) {
188 Ok(()) => return,
189 Err(err) => {
190 eprintln!("Error: {}", err);
191 process::exit(1);
192 }
193 }
194 }
195
196 // `zed --printenv` Outputs environment variables as JSON to stdout
197 if args.printenv {
198 util::shell_env::print_env();
199 return;
200 }
201
202 if args.dump_all_actions {
203 dump_all_gpui_actions();
204 return;
205 }
206
207 // Set custom data directory.
208 if let Some(dir) = &args.user_data_dir {
209 paths::set_custom_data_dir(dir);
210 }
211
212 #[cfg(all(not(debug_assertions), target_os = "windows"))]
213 unsafe {
214 use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
215
216 if args.foreground {
217 let _ = AttachConsole(ATTACH_PARENT_PROCESS);
218 }
219 }
220
221 let file_errors = init_paths();
222 if !file_errors.is_empty() {
223 files_not_created_on_launch(file_errors);
224 return;
225 }
226
227 zlog::init();
228 if stdout_is_a_pty() {
229 zlog::init_output_stdout();
230 } else {
231 let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));
232 if let Err(err) = result {
233 eprintln!("Could not open log file: {}... Defaulting to stdout", err);
234 zlog::init_output_stdout();
235 };
236 }
237
238 let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"));
239 let app_commit_sha =
240 option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
241
242 if args.system_specs {
243 let system_specs = system_specs::SystemSpecs::new_stateless(
244 app_version,
245 app_commit_sha,
246 *release_channel::RELEASE_CHANNEL,
247 );
248 println!("Zed System Specs (from CLI):\n{}", system_specs);
249 return;
250 }
251
252 log::info!(
253 "========== starting zed version {}, sha {} ==========",
254 app_version,
255 app_commit_sha
256 .as_ref()
257 .map(|sha| sha.short())
258 .as_deref()
259 .unwrap_or("unknown"),
260 );
261
262 let app = Application::new().with_assets(Assets);
263
264 let system_id = app.background_executor().block(system_id()).ok();
265 let installation_id = app.background_executor().block(installation_id()).ok();
266 let session_id = Uuid::new_v4().to_string();
267 let session = app.background_executor().block(Session::new());
268
269 app.background_executor()
270 .spawn(crashes::init(InitCrashHandler {
271 session_id: session_id.clone(),
272 zed_version: app_version.to_string(),
273 release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
274 commit_sha: app_commit_sha
275 .as_ref()
276 .map(|sha| sha.full())
277 .unwrap_or_else(|| "no sha".to_owned()),
278 }))
279 .detach();
280
281 let (open_listener, mut open_rx) = OpenListener::new();
282
283 let failed_single_instance_check = if *zed_env_vars::ZED_STATELESS
284 || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
285 {
286 false
287 } else {
288 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
289 {
290 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
291 }
292
293 #[cfg(target_os = "windows")]
294 {
295 !crate::zed::windows_only_instance::handle_single_instance(open_listener.clone(), &args)
296 }
297
298 #[cfg(target_os = "macos")]
299 {
300 use zed::mac_only_instance::*;
301 ensure_only_instance() != IsOnlyInstance::Yes
302 }
303 };
304 if failed_single_instance_check {
305 println!("zed is already running");
306 return;
307 }
308
309 let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
310 let git_binary_path =
311 if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
312 app.path_for_auxiliary_executable("git")
313 .context("could not find git binary path")
314 .log_err()
315 } else {
316 None
317 };
318 log::info!("Using git binary path: {:?}", git_binary_path);
319
320 let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
321 let user_settings_file_rx = watch_config_file(
322 &app.background_executor(),
323 fs.clone(),
324 paths::settings_file().clone(),
325 );
326 let global_settings_file_rx = watch_config_file(
327 &app.background_executor(),
328 fs.clone(),
329 paths::global_settings_file().clone(),
330 );
331 let user_keymap_file_rx = watch_config_file(
332 &app.background_executor(),
333 fs.clone(),
334 paths::keymap_file().clone(),
335 );
336
337 let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
338 if !stdout_is_a_pty() {
339 app.background_executor()
340 .spawn(async {
341 #[cfg(unix)]
342 util::load_login_shell_environment().log_err();
343 shell_env_loaded_tx.send(()).ok();
344 })
345 .detach()
346 } else {
347 drop(shell_env_loaded_tx)
348 }
349
350 app.on_open_urls({
351 let open_listener = open_listener.clone();
352 move |urls| {
353 open_listener.open(RawOpenRequest {
354 urls,
355 diff_paths: Vec::new(),
356 ..Default::default()
357 })
358 }
359 });
360 app.on_reopen(move |cx| {
361 if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
362 {
363 cx.spawn({
364 let app_state = app_state;
365 async move |cx| {
366 if let Err(e) = restore_or_create_workspace(app_state, cx).await {
367 fail_to_open_window_async(e, cx)
368 }
369 }
370 })
371 .detach();
372 }
373 });
374
375 app.run(move |cx| {
376 menu::init();
377 zed_actions::init();
378
379 release_channel::init(app_version, cx);
380 gpui_tokio::init(cx);
381 if let Some(app_commit_sha) = app_commit_sha {
382 AppCommitSha::set_global(app_commit_sha, cx);
383 }
384 settings::init(cx);
385 zlog_settings::init(cx);
386 handle_settings_file_changes(
387 user_settings_file_rx,
388 global_settings_file_rx,
389 cx,
390 handle_settings_changed,
391 );
392 handle_keymap_file_changes(user_keymap_file_rx, cx);
393 client::init_settings(cx);
394 let user_agent = format!(
395 "Zed/{} ({}; {})",
396 AppVersion::global(cx),
397 std::env::consts::OS,
398 std::env::consts::ARCH
399 );
400 let proxy_url = ProxySettings::get_global(cx).proxy_url();
401 let http = {
402 let _guard = Tokio::handle(cx).enter();
403
404 ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
405 .expect("could not start HTTP client")
406 };
407 cx.set_http_client(Arc::new(http));
408
409 <dyn Fs>::set_global(fs.clone(), cx);
410
411 GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
412 git_hosting_providers::init(cx);
413
414 OpenListener::set_global(cx, open_listener.clone());
415
416 extension::init(cx);
417 let extension_host_proxy = ExtensionHostProxy::global(cx);
418
419 let client = Client::production(cx);
420 cx.set_http_client(client.http_client());
421 let mut languages = LanguageRegistry::new(cx.background_executor().clone());
422 languages.set_language_server_download_dir(paths::languages_dir().clone());
423 let languages = Arc::new(languages);
424 let (mut tx, rx) = watch::channel(None);
425 cx.observe_global::<SettingsStore>(move |cx| {
426 let settings = &ProjectSettings::get_global(cx).node;
427 let options = NodeBinaryOptions {
428 allow_path_lookup: !settings.ignore_system_version,
429 // TODO: Expose this setting
430 allow_binary_download: true,
431 use_paths: settings.path.as_ref().map(|node_path| {
432 let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
433 let npm_path = settings
434 .npm_path
435 .as_ref()
436 .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
437 (
438 node_path.clone(),
439 npm_path.unwrap_or_else(|| {
440 let base_path = PathBuf::new();
441 node_path.parent().unwrap_or(&base_path).join("npm")
442 }),
443 )
444 }),
445 };
446 tx.send(Some(options)).log_err();
447 })
448 .detach();
449 let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
450
451 debug_adapter_extension::init(extension_host_proxy.clone(), cx);
452 language::init(cx);
453 languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx);
454 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
455 let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
456
457 language_extension::init(
458 language_extension::LspAccess::ViaWorkspaces({
459 let workspace_store = workspace_store.clone();
460 Arc::new(move |cx: &mut App| {
461 workspace_store.update(cx, |workspace_store, cx| {
462 workspace_store
463 .workspaces()
464 .iter()
465 .map(|workspace| {
466 workspace.update(cx, |workspace, _, cx| {
467 workspace.project().read(cx).lsp_store()
468 })
469 })
470 .collect()
471 })
472 })
473 }),
474 extension_host_proxy.clone(),
475 languages.clone(),
476 );
477
478 Client::set_global(client.clone(), cx);
479
480 zed::init(cx);
481 project::Project::init(&client, cx);
482 debugger_ui::init(cx);
483 debugger_tools::init(cx);
484 client::init(&client, cx);
485 let telemetry = client.telemetry();
486 telemetry.start(
487 system_id.as_ref().map(|id| id.to_string()),
488 installation_id.as_ref().map(|id| id.to_string()),
489 session_id.clone(),
490 cx,
491 );
492
493 // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
494 if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
495 match (&system_id, &installation_id) {
496 (IdType::New(_), IdType::New(_)) => {
497 telemetry::event!("App First Opened");
498 telemetry::event!("App First Opened For Release Channel");
499 }
500 (IdType::Existing(_), IdType::New(_)) => {
501 telemetry::event!("App First Opened For Release Channel");
502 }
503 (_, IdType::Existing(_)) => {
504 telemetry::event!("App Opened");
505 }
506 }
507 }
508 let app_session = cx.new(|cx| AppSession::new(session, cx));
509
510 let app_state = Arc::new(AppState {
511 languages,
512 client: client.clone(),
513 user_store,
514 fs: fs.clone(),
515 build_window_options,
516 workspace_store,
517 node_runtime,
518 session: app_session,
519 });
520 AppState::set_global(Arc::downgrade(&app_state), cx);
521
522 auto_update::init(client.http_client(), cx);
523 dap_adapters::init(cx);
524 auto_update_ui::init(cx);
525 reliability::init(
526 client.http_client(),
527 system_id.as_ref().map(|id| id.to_string()),
528 cx,
529 );
530
531 SystemAppearance::init(cx);
532 theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
533 theme_extension::init(
534 extension_host_proxy.clone(),
535 ThemeRegistry::global(cx),
536 cx.background_executor().clone(),
537 );
538 command_palette::init(cx);
539 let copilot_language_server_id = app_state.languages.next_language_server_id();
540 copilot::init(
541 copilot_language_server_id,
542 app_state.fs.clone(),
543 app_state.client.http_client(),
544 app_state.node_runtime.clone(),
545 cx,
546 );
547 supermaven::init(app_state.client.clone(), cx);
548 language_model::init(app_state.client.clone(), cx);
549 language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
550 agent_settings::init(cx);
551 acp_tools::init(cx);
552 edit_prediction_tools::init(cx);
553 web_search::init(cx);
554 web_search_providers::init(app_state.client.clone(), cx);
555 snippet_provider::init(cx);
556 edit_prediction_registry::init(app_state.client.clone(), app_state.user_store.clone(), cx);
557 let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
558 agent_ui::init(
559 app_state.fs.clone(),
560 app_state.client.clone(),
561 prompt_builder.clone(),
562 app_state.languages.clone(),
563 false,
564 cx,
565 );
566 assistant_tools::init(app_state.client.http_client(), cx);
567 repl::init(app_state.fs.clone(), cx);
568 extension_host::init(
569 extension_host_proxy,
570 app_state.fs.clone(),
571 app_state.client.clone(),
572 app_state.node_runtime.clone(),
573 cx,
574 );
575 recent_projects::init(cx);
576
577 load_embedded_fonts(cx);
578
579 app_state.languages.set_theme(cx.theme().clone());
580 editor::init(cx);
581 image_viewer::init(cx);
582 repl::notebook::init(cx);
583 diagnostics::init(cx);
584
585 audio::init(cx);
586 workspace::init(app_state.clone(), cx);
587 ui_prompt::init(cx);
588
589 go_to_line::init(cx);
590 file_finder::init(cx);
591 tab_switcher::init(cx);
592 outline::init(cx);
593 project_symbols::init(cx);
594 project_panel::init(cx);
595 outline_panel::init(cx);
596 tasks_ui::init(cx);
597 snippets_ui::init(cx);
598 channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
599 search::init(cx);
600 vim::init(cx);
601 terminal_view::init(cx);
602 journal::init(app_state.clone(), cx);
603 language_selector::init(cx);
604 line_ending_selector::init(cx);
605 toolchain_selector::init(cx);
606 theme_selector::init(cx);
607 settings_profile_selector::init(cx);
608 language_tools::init(cx);
609 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
610 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
611 collab_ui::init(&app_state, cx);
612 git_ui::init(cx);
613 jj_ui::init(cx);
614 feedback::init(cx);
615 markdown_preview::init(cx);
616 svg_preview::init(cx);
617 onboarding::init(cx);
618 keymap_editor::init(cx);
619 extensions_ui::init(cx);
620 zeta::init(cx);
621 inspector_ui::init(app_state.clone(), cx);
622
623 cx.observe_global::<SettingsStore>({
624 let fs = fs.clone();
625 let languages = app_state.languages.clone();
626 let http = app_state.client.http_client();
627 let client = app_state.client.clone();
628 move |cx| {
629 for &mut window in cx.windows().iter_mut() {
630 let background_appearance = cx.theme().window_background_appearance();
631 window
632 .update(cx, |_, window, _| {
633 window.set_background_appearance(background_appearance)
634 })
635 .ok();
636 }
637
638 eager_load_active_theme_and_icon_theme(fs.clone(), cx);
639
640 languages.set_theme(cx.theme().clone());
641 let new_host = &client::ClientSettings::get_global(cx).server_url;
642 if &http.base_url() != new_host {
643 http.set_base_url(new_host);
644 if client.status().borrow().is_connected() {
645 client.reconnect(&cx.to_async());
646 }
647 }
648 }
649 })
650 .detach();
651 telemetry::event!(
652 "Settings Changed",
653 setting = "theme",
654 value = cx.theme().name.to_string()
655 );
656 telemetry::event!(
657 "Settings Changed",
658 setting = "keymap",
659 value = BaseKeymap::get_global(cx).to_string()
660 );
661 telemetry.flush_events().detach();
662
663 let fs = app_state.fs.clone();
664 load_user_themes_in_background(fs.clone(), cx);
665 watch_themes(fs.clone(), cx);
666 watch_languages(fs.clone(), app_state.languages.clone(), cx);
667
668 cx.set_menus(app_menus());
669 initialize_workspace(app_state.clone(), prompt_builder, cx);
670
671 cx.activate(true);
672
673 cx.spawn({
674 let client = app_state.client.clone();
675 async move |cx| authenticate(client, cx).await
676 })
677 .detach_and_log_err(cx);
678
679 let urls: Vec<_> = args
680 .paths_or_urls
681 .iter()
682 .map(|arg| parse_url_arg(arg, cx))
683 .collect();
684
685 let diff_paths: Vec<[String; 2]> = args
686 .diff
687 .chunks(2)
688 .map(|chunk| [chunk[0].clone(), chunk[1].clone()])
689 .collect();
690
691 #[cfg(target_os = "windows")]
692 let wsl = args.wsl;
693 #[cfg(not(target_os = "windows"))]
694 let wsl = None;
695
696 if !urls.is_empty() || !diff_paths.is_empty() {
697 open_listener.open(RawOpenRequest {
698 urls,
699 diff_paths,
700 wsl,
701 })
702 }
703
704 match open_rx
705 .try_next()
706 .ok()
707 .flatten()
708 .and_then(|request| OpenRequest::parse(request, cx).log_err())
709 {
710 Some(request) => {
711 handle_open_request(request, app_state.clone(), cx);
712 }
713 None => {
714 cx.spawn({
715 let app_state = app_state.clone();
716 async move |cx| {
717 if let Err(e) = restore_or_create_workspace(app_state, cx).await {
718 fail_to_open_window_async(e, cx)
719 }
720 }
721 })
722 .detach();
723 }
724 }
725
726 let app_state = app_state.clone();
727
728 crate::zed::component_preview::init(app_state.clone(), cx);
729
730 cx.spawn(async move |cx| {
731 while let Some(urls) = open_rx.next().await {
732 cx.update(|cx| {
733 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
734 handle_open_request(request, app_state.clone(), cx);
735 }
736 })
737 .ok();
738 }
739 })
740 .detach();
741 });
742}
743
744fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
745 if let Some(kind) = request.kind {
746 match kind {
747 OpenRequestKind::CliConnection(connection) => {
748 cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
749 .detach();
750 }
751 OpenRequestKind::Extension { extension_id } => {
752 cx.spawn(async move |cx| {
753 let workspace =
754 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
755 workspace.update(cx, |_, window, cx| {
756 window.dispatch_action(
757 Box::new(zed_actions::Extensions {
758 category_filter: None,
759 id: Some(extension_id),
760 }),
761 cx,
762 );
763 })
764 })
765 .detach_and_log_err(cx);
766 }
767 OpenRequestKind::AgentPanel => {
768 cx.spawn(async move |cx| {
769 let workspace =
770 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
771 workspace.update(cx, |workspace, window, cx| {
772 if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
773 panel.focus_handle(cx).focus(window);
774 }
775 })
776 })
777 .detach_and_log_err(cx);
778 }
779 OpenRequestKind::DockMenuAction { index } => {
780 cx.perform_dock_menu_action(index);
781 }
782 }
783
784 return;
785 }
786
787 if let Some(connection_options) = request.remote_connection {
788 cx.spawn(async move |cx| {
789 let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
790 open_remote_project(
791 connection_options,
792 paths,
793 app_state,
794 workspace::OpenOptions::default(),
795 cx,
796 )
797 .await
798 })
799 .detach_and_log_err(cx);
800 return;
801 }
802
803 let mut task = None;
804 if !request.open_paths.is_empty() || !request.diff_paths.is_empty() {
805 let app_state = app_state.clone();
806 task = Some(cx.spawn(async move |cx| {
807 let paths_with_position =
808 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
809 let (_window, results) = open_paths_with_positions(
810 &paths_with_position,
811 &request.diff_paths,
812 app_state,
813 workspace::OpenOptions::default(),
814 cx,
815 )
816 .await?;
817 for result in results.into_iter().flatten() {
818 if let Err(err) = result {
819 log::error!("Error opening path: {err}",);
820 }
821 }
822 anyhow::Ok(())
823 }));
824 }
825
826 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
827 cx.spawn(async move |cx| {
828 let result = maybe!(async {
829 if let Some(task) = task {
830 task.await?;
831 }
832 let client = app_state.client.clone();
833 // we continue even if authentication fails as join_channel/ open channel notes will
834 // show a visible error message.
835 authenticate(client, cx).await.log_err();
836
837 if let Some(channel_id) = request.join_channel {
838 cx.update(|cx| {
839 workspace::join_channel(
840 client::ChannelId(channel_id),
841 app_state.clone(),
842 None,
843 cx,
844 )
845 })?
846 .await?;
847 }
848
849 let workspace_window =
850 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
851 let workspace = workspace_window.entity(cx)?;
852
853 let mut promises = Vec::new();
854 for (channel_id, heading) in request.open_channel_notes {
855 promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
856 ChannelView::open(
857 client::ChannelId(channel_id),
858 heading,
859 workspace.clone(),
860 window,
861 cx,
862 )
863 .log_err()
864 })?)
865 }
866 future::join_all(promises).await;
867 anyhow::Ok(())
868 })
869 .await;
870 if let Err(err) = result {
871 fail_to_open_window_async(err, cx);
872 }
873 })
874 .detach()
875 } else if let Some(task) = task {
876 cx.spawn(async move |cx| {
877 if let Err(err) = task.await {
878 fail_to_open_window_async(err, cx);
879 }
880 })
881 .detach();
882 }
883}
884
885async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> Result<()> {
886 if stdout_is_a_pty() {
887 if client::IMPERSONATE_LOGIN.is_some() {
888 client.sign_in_with_optional_connect(false, cx).await?;
889 } else if client.has_credentials(cx).await {
890 client.sign_in_with_optional_connect(true, cx).await?;
891 }
892 } else if client.has_credentials(cx).await {
893 client.sign_in_with_optional_connect(true, cx).await?;
894 }
895
896 Ok(())
897}
898
899async fn system_id() -> Result<IdType> {
900 let key_name = "system_id".to_string();
901
902 if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
903 return Ok(IdType::Existing(system_id));
904 }
905
906 let system_id = Uuid::new_v4().to_string();
907
908 GLOBAL_KEY_VALUE_STORE
909 .write_kvp(key_name, system_id.clone())
910 .await?;
911
912 Ok(IdType::New(system_id))
913}
914
915async fn installation_id() -> Result<IdType> {
916 let legacy_key_name = "device_id".to_string();
917 let key_name = "installation_id".to_string();
918
919 // Migrate legacy key to new key
920 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
921 KEY_VALUE_STORE
922 .write_kvp(key_name, installation_id.clone())
923 .await?;
924 KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
925 return Ok(IdType::Existing(installation_id));
926 }
927
928 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
929 return Ok(IdType::Existing(installation_id));
930 }
931
932 let installation_id = Uuid::new_v4().to_string();
933
934 KEY_VALUE_STORE
935 .write_kvp(key_name, installation_id.clone())
936 .await?;
937
938 Ok(IdType::New(installation_id))
939}
940
941async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
942 if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
943 let use_system_window_tabs = cx
944 .update(|cx| WorkspaceSettings::get_global(cx).use_system_window_tabs)
945 .unwrap_or(false);
946 let mut results: Vec<Result<(), Error>> = Vec::new();
947 let mut tasks = Vec::new();
948
949 for (index, (location, paths)) in locations.into_iter().enumerate() {
950 match location {
951 SerializedWorkspaceLocation::Local => {
952 let app_state = app_state.clone();
953 let task = cx.spawn(async move |cx| {
954 let open_task = cx.update(|cx| {
955 workspace::open_paths(
956 &paths.paths(),
957 app_state,
958 workspace::OpenOptions::default(),
959 cx,
960 )
961 })?;
962 open_task.await.map(|_| ())
963 });
964
965 // If we're using system window tabs and this is the first workspace,
966 // wait for it to finish so that the other windows can be added as tabs.
967 if use_system_window_tabs && index == 0 {
968 results.push(task.await);
969 } else {
970 tasks.push(task);
971 }
972 }
973 SerializedWorkspaceLocation::Remote(mut connection_options) => {
974 let app_state = app_state.clone();
975 if let RemoteConnectionOptions::Ssh(options) = &mut connection_options {
976 cx.update(|cx| {
977 SshSettings::get_global(cx)
978 .fill_connection_options_from_settings(options)
979 })?;
980 }
981 let task = cx.spawn(async move |cx| {
982 recent_projects::open_remote_project(
983 connection_options,
984 paths.paths().into_iter().map(PathBuf::from).collect(),
985 app_state,
986 workspace::OpenOptions::default(),
987 cx,
988 )
989 .await
990 .map_err(|e| anyhow::anyhow!(e))
991 });
992 tasks.push(task);
993 }
994 }
995 }
996
997 // Wait for all workspaces to open concurrently
998 results.extend(future::join_all(tasks).await);
999
1000 // Show notifications for any errors that occurred
1001 let mut error_count = 0;
1002 for result in results {
1003 if let Err(e) = result {
1004 log::error!("Failed to restore workspace: {}", e);
1005 error_count += 1;
1006 }
1007 }
1008
1009 if error_count > 0 {
1010 let message = if error_count == 1 {
1011 "Failed to restore 1 workspace. Check logs for details.".to_string()
1012 } else {
1013 format!(
1014 "Failed to restore {} workspaces. Check logs for details.",
1015 error_count
1016 )
1017 };
1018
1019 // Try to find an active workspace to show the toast
1020 let toast_shown = cx
1021 .update(|cx| {
1022 if let Some(window) = cx.active_window()
1023 && let Some(workspace) = window.downcast::<Workspace>()
1024 {
1025 workspace
1026 .update(cx, |workspace, _, cx| {
1027 workspace.show_toast(
1028 Toast::new(NotificationId::unique::<()>(), message),
1029 cx,
1030 )
1031 })
1032 .ok();
1033 return true;
1034 }
1035 false
1036 })
1037 .unwrap_or(false);
1038
1039 // If we couldn't show a toast (no windows opened successfully),
1040 // we've already logged the errors above, so the user can check logs
1041 if !toast_shown {
1042 log::error!(
1043 "Failed to show notification for window restoration errors, because no workspace windows were available."
1044 );
1045 }
1046 }
1047 } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
1048 cx.update(|cx| show_onboarding_view(app_state, cx))?.await?;
1049 } else {
1050 cx.update(|cx| {
1051 workspace::open_new(
1052 Default::default(),
1053 app_state,
1054 cx,
1055 |workspace, window, cx| {
1056 Editor::new_file(workspace, &Default::default(), window, cx)
1057 },
1058 )
1059 })?
1060 .await?;
1061 }
1062
1063 Ok(())
1064}
1065
1066pub(crate) async fn restorable_workspace_locations(
1067 cx: &mut AsyncApp,
1068 app_state: &Arc<AppState>,
1069) -> Option<Vec<(SerializedWorkspaceLocation, PathList)>> {
1070 let mut restore_behavior = cx
1071 .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
1072 .ok()?;
1073
1074 let session_handle = app_state.session.clone();
1075 let (last_session_id, last_session_window_stack) = cx
1076 .update(|cx| {
1077 let session = session_handle.read(cx);
1078
1079 (
1080 session.last_session_id().map(|id| id.to_string()),
1081 session.last_session_window_stack(),
1082 )
1083 })
1084 .ok()?;
1085
1086 if last_session_id.is_none()
1087 && matches!(
1088 restore_behavior,
1089 workspace::RestoreOnStartupBehavior::LastSession
1090 )
1091 {
1092 restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
1093 }
1094
1095 match restore_behavior {
1096 workspace::RestoreOnStartupBehavior::LastWorkspace => {
1097 workspace::last_opened_workspace_location()
1098 .await
1099 .map(|location| vec![location])
1100 }
1101 workspace::RestoreOnStartupBehavior::LastSession => {
1102 if let Some(last_session_id) = last_session_id {
1103 let ordered = last_session_window_stack.is_some();
1104
1105 let mut locations = workspace::last_session_workspace_locations(
1106 &last_session_id,
1107 last_session_window_stack,
1108 )
1109 .filter(|locations| !locations.is_empty());
1110
1111 // Since last_session_window_order returns the windows ordered front-to-back
1112 // we need to open the window that was frontmost last.
1113 if ordered && let Some(locations) = locations.as_mut() {
1114 locations.reverse();
1115 }
1116
1117 locations
1118 } else {
1119 None
1120 }
1121 }
1122 _ => None,
1123 }
1124}
1125
1126fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
1127 [
1128 paths::config_dir(),
1129 paths::extensions_dir(),
1130 paths::languages_dir(),
1131 paths::debug_adapters_dir(),
1132 paths::database_dir(),
1133 paths::logs_dir(),
1134 paths::temp_dir(),
1135 ]
1136 .into_iter()
1137 .fold(HashMap::default(), |mut errors, path| {
1138 if let Err(e) = std::fs::create_dir_all(path) {
1139 errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
1140 }
1141 errors
1142 })
1143}
1144
1145pub fn stdout_is_a_pty() -> bool {
1146 std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
1147}
1148
1149#[derive(Parser, Debug)]
1150#[command(name = "zed", disable_version_flag = true)]
1151struct Args {
1152 /// A sequence of space-separated paths or urls that you want to open.
1153 ///
1154 /// Use `path:line:row` syntax to open a file at a specific location.
1155 /// Non-existing paths and directories will ignore `:line:row` suffix.
1156 ///
1157 /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1158 paths_or_urls: Vec<String>,
1159
1160 /// Pairs of file paths to diff. Can be specified multiple times.
1161 #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
1162 diff: Vec<String>,
1163
1164 /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1165 /// This overrides the default platform-specific data directory location.
1166 /// On macOS, the default is `~/Library/Application Support/Zed`.
1167 /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
1168 /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
1169 #[arg(long, value_name = "DIR")]
1170 user_data_dir: Option<String>,
1171
1172 /// The username and WSL distribution to use when opening paths. If not specified,
1173 /// Zed will attempt to open the paths directly.
1174 ///
1175 /// The username is optional, and if not specified, the default user for the distribution
1176 /// will be used.
1177 ///
1178 /// Example: `me@Ubuntu` or `Ubuntu`.
1179 ///
1180 /// WARN: You should not fill in this field by hand.
1181 #[cfg(target_os = "windows")]
1182 #[arg(long, value_name = "USER@DISTRO")]
1183 wsl: Option<String>,
1184
1185 /// Instructs zed to run as a dev server on this machine. (not implemented)
1186 #[arg(long)]
1187 dev_server_token: Option<String>,
1188
1189 /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
1190 /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
1191 #[arg(long)]
1192 system_specs: bool,
1193
1194 /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1195 /// by having Zed act like netcat communicating over a Unix socket.
1196 #[arg(long, hide = true)]
1197 askpass: Option<String>,
1198
1199 /// Used for the MCP Server, to remove the need for netcat as a dependency,
1200 /// by having Zed act like netcat communicating over a Unix socket.
1201 #[arg(long, hide = true)]
1202 nc: Option<String>,
1203
1204 /// Used for recording minidumps on crashes by having Zed run a separate
1205 /// process communicating over a socket.
1206 #[arg(long, hide = true)]
1207 crash_handler: Option<PathBuf>,
1208
1209 /// Run zed in the foreground, only used on Windows, to match the behavior on macOS.
1210 #[arg(long)]
1211 #[cfg(target_os = "windows")]
1212 #[arg(hide = true)]
1213 foreground: bool,
1214
1215 /// The dock action to perform. This is used on Windows only.
1216 #[arg(long)]
1217 #[cfg(target_os = "windows")]
1218 #[arg(hide = true)]
1219 dock_action: Option<usize>,
1220
1221 #[arg(long, hide = true)]
1222 dump_all_actions: bool,
1223
1224 /// Output current environment variables as JSON to stdout
1225 #[arg(long, hide = true)]
1226 printenv: bool,
1227}
1228
1229#[derive(Clone, Debug)]
1230enum IdType {
1231 New(String),
1232 Existing(String),
1233}
1234
1235impl ToString for IdType {
1236 fn to_string(&self) -> String {
1237 match self {
1238 IdType::New(id) | IdType::Existing(id) => id.clone(),
1239 }
1240 }
1241}
1242
1243fn parse_url_arg(arg: &str, cx: &App) -> String {
1244 match std::fs::canonicalize(Path::new(&arg)) {
1245 Ok(path) => format!("file://{}", path.display()),
1246 Err(_) => {
1247 if arg.starts_with("file://")
1248 || arg.starts_with("zed-cli://")
1249 || arg.starts_with("ssh://")
1250 || parse_zed_link(arg, cx).is_some()
1251 {
1252 arg.into()
1253 } else {
1254 format!("file://{arg}")
1255 }
1256 }
1257 }
1258}
1259
1260fn load_embedded_fonts(cx: &App) {
1261 let asset_source = cx.asset_source();
1262 let font_paths = asset_source.list("fonts").unwrap();
1263 let embedded_fonts = Mutex::new(Vec::new());
1264 let executor = cx.background_executor();
1265
1266 executor.block(executor.scoped(|scope| {
1267 for font_path in &font_paths {
1268 if !font_path.ends_with(".ttf") {
1269 continue;
1270 }
1271
1272 scope.spawn(async {
1273 let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1274 embedded_fonts.lock().push(font_bytes);
1275 });
1276 }
1277 }));
1278
1279 cx.text_system()
1280 .add_fonts(embedded_fonts.into_inner())
1281 .unwrap();
1282}
1283
1284/// Eagerly loads the active theme and icon theme based on the selections in the
1285/// theme settings.
1286///
1287/// This fast path exists to load these themes as soon as possible so the user
1288/// doesn't see the default themes while waiting on extensions to load.
1289fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
1290 let extension_store = ExtensionStore::global(cx);
1291 let theme_registry = ThemeRegistry::global(cx);
1292 let theme_settings = ThemeSettings::get_global(cx);
1293 let appearance = SystemAppearance::global(cx).0;
1294
1295 if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
1296 let theme_name = theme_selection.theme(appearance);
1297 if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_)))
1298 && let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name)
1299 {
1300 cx.spawn({
1301 let theme_registry = theme_registry.clone();
1302 let fs = fs.clone();
1303 async move |cx| {
1304 theme_registry.load_user_theme(&theme_path, fs).await?;
1305
1306 cx.update(|cx| {
1307 ThemeSettings::reload_current_theme(cx);
1308 })
1309 }
1310 })
1311 .detach_and_log_err(cx);
1312 }
1313 }
1314
1315 if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
1316 let icon_theme_name = icon_theme_selection.icon_theme(appearance);
1317 if matches!(
1318 theme_registry.get_icon_theme(icon_theme_name),
1319 Err(IconThemeNotFoundError(_))
1320 ) && let Some((icon_theme_path, icons_root_path)) = extension_store
1321 .read(cx)
1322 .path_to_extension_icon_theme(icon_theme_name)
1323 {
1324 cx.spawn({
1325 let fs = fs.clone();
1326 async move |cx| {
1327 theme_registry
1328 .load_icon_theme(&icon_theme_path, &icons_root_path, fs)
1329 .await?;
1330
1331 cx.update(|cx| {
1332 ThemeSettings::reload_current_icon_theme(cx);
1333 })
1334 }
1335 })
1336 .detach_and_log_err(cx);
1337 }
1338 }
1339}
1340
1341/// Spawns a background task to load the user themes from the themes directory.
1342fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1343 cx.spawn({
1344 let fs = fs.clone();
1345 async move |cx| {
1346 if let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx)).log_err() {
1347 let themes_dir = paths::themes_dir().as_ref();
1348 match fs
1349 .metadata(themes_dir)
1350 .await
1351 .ok()
1352 .flatten()
1353 .map(|m| m.is_dir)
1354 {
1355 Some(is_dir) => {
1356 anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1357 }
1358 None => {
1359 fs.create_dir(themes_dir).await.with_context(|| {
1360 format!("Failed to create themes dir at path {themes_dir:?}")
1361 })?;
1362 }
1363 }
1364 theme_registry.load_user_themes(themes_dir, fs).await?;
1365 cx.update(ThemeSettings::reload_current_theme)?;
1366 }
1367 anyhow::Ok(())
1368 }
1369 })
1370 .detach_and_log_err(cx);
1371}
1372
1373/// Spawns a background task to watch the themes directory for changes.
1374fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1375 use std::time::Duration;
1376 cx.spawn(async move |cx| {
1377 let (mut events, _) = fs
1378 .watch(paths::themes_dir(), Duration::from_millis(100))
1379 .await;
1380
1381 while let Some(paths) = events.next().await {
1382 for event in paths {
1383 if fs.metadata(&event.path).await.ok().flatten().is_some()
1384 && let Some(theme_registry) =
1385 cx.update(|cx| ThemeRegistry::global(cx)).log_err()
1386 && let Some(()) = theme_registry
1387 .load_user_theme(&event.path, fs.clone())
1388 .await
1389 .log_err()
1390 {
1391 cx.update(ThemeSettings::reload_current_theme).log_err();
1392 }
1393 }
1394 }
1395 })
1396 .detach()
1397}
1398
1399#[cfg(debug_assertions)]
1400fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1401 use std::time::Duration;
1402
1403 cx.background_spawn(async move {
1404 let languages_src = Path::new("crates/languages/src");
1405 let Some(languages_src) = fs.canonicalize(languages_src).await.log_err() else {
1406 return;
1407 };
1408
1409 let (mut events, watcher) = fs.watch(&languages_src, Duration::from_millis(100)).await;
1410
1411 // add subdirectories since fs.watch is not recursive on Linux
1412 if let Some(mut paths) = fs.read_dir(&languages_src).await.log_err() {
1413 while let Some(path) = paths.next().await {
1414 if let Some(path) = path.log_err()
1415 && fs.is_dir(&path).await
1416 {
1417 watcher.add(&path).log_err();
1418 }
1419 }
1420 }
1421
1422 while let Some(event) = events.next().await {
1423 let has_language_file = event
1424 .iter()
1425 .any(|event| event.path.extension().is_some_and(|ext| ext == "scm"));
1426 if has_language_file {
1427 languages.reload();
1428 }
1429 }
1430 })
1431 .detach();
1432}
1433
1434#[cfg(not(debug_assertions))]
1435fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}
1436
1437fn dump_all_gpui_actions() {
1438 #[derive(Debug, serde::Serialize)]
1439 struct ActionDef {
1440 name: &'static str,
1441 human_name: String,
1442 aliases: &'static [&'static str],
1443 documentation: Option<&'static str>,
1444 }
1445 let mut actions = gpui::generate_list_of_all_registered_actions()
1446 .map(|action| ActionDef {
1447 name: action.name,
1448 human_name: command_palette::humanize_action_name(action.name),
1449 aliases: action.deprecated_aliases,
1450 documentation: action.documentation,
1451 })
1452 .collect::<Vec<ActionDef>>();
1453
1454 actions.sort_by_key(|a| a.name);
1455
1456 io::Write::write(
1457 &mut std::io::stdout(),
1458 serde_json::to_string_pretty(&actions).unwrap().as_bytes(),
1459 )
1460 .unwrap();
1461}