main.rs

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