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