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