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