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