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