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