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