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