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