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