main.rs

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