main.rs

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