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