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 _, 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,
  41};
  42use theme::{ActiveTheme, GlobalTheme, ThemeRegistry};
  43use util::{ResultExt, TryFutureExt, maybe};
  44use uuid::Uuid;
  45use workspace::{
  46    AppState, PathList, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceSettings,
  47    WorkspaceStore, notifications::NotificationId,
  48};
  49use zed::{
  50    OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
  51    derive_paths_with_position, edit_prediction_registry, handle_cli_connection,
  52    handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes,
  53    initialize_workspace, open_paths_with_positions,
  54};
  55
  56use crate::zed::{OpenRequestKind, eager_load_active_theme_and_icon_theme};
  57
  58#[cfg(feature = "mimalloc")]
  59#[global_allocator]
  60static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
  61
  62fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
  63    let message = "Zed failed to launch";
  64    let error_details = errors
  65        .into_iter()
  66        .flat_map(|(kind, paths)| {
  67            #[allow(unused_mut)] // for non-unix platforms
  68            let mut error_kind_details = match paths.len() {
  69                0 => return None,
  70                1 => format!(
  71                    "{kind} when creating directory {:?}",
  72                    paths.first().expect("match arm checks for a single entry")
  73                ),
  74                _many => format!("{kind} when creating directories {paths:?}"),
  75            };
  76
  77            #[cfg(unix)]
  78            {
  79                if kind == io::ErrorKind::PermissionDenied {
  80                    error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\
  81                        \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`");
  82                }
  83            }
  84
  85            Some(error_kind_details)
  86        })
  87        .collect::<Vec<_>>().join("\n\n");
  88
  89    eprintln!("{message}: {error_details}");
  90    Application::new().run(move |cx| {
  91        if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
  92            cx.new(|_| gpui::Empty)
  93        }) {
  94            window
  95                .update(cx, |_, window, cx| {
  96                    let response = window.prompt(
  97                        gpui::PromptLevel::Critical,
  98                        message,
  99                        Some(&error_details),
 100                        &["Exit"],
 101                        cx,
 102                    );
 103
 104                    cx.spawn_in(window, async move |_, cx| {
 105                        response.await?;
 106                        cx.update(|_, cx| cx.quit())
 107                    })
 108                    .detach_and_log_err(cx);
 109                })
 110                .log_err();
 111        } else {
 112            fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx)
 113        }
 114    })
 115}
 116
 117fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {
 118    cx.update(|cx| fail_to_open_window(e, cx)).log_err();
 119}
 120
 121fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
 122    eprintln!(
 123        "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
 124    );
 125    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
 126    {
 127        process::exit(1);
 128    }
 129
 130    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 131    {
 132        use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
 133        _cx.spawn(async move |_cx| {
 134            let Ok(proxy) = NotificationProxy::new().await else {
 135                process::exit(1);
 136            };
 137
 138            let notification_id = "dev.zed.Oops";
 139            proxy
 140                .add_notification(
 141                    notification_id,
 142                    Notification::new("Zed failed to launch")
 143                        .body(Some(
 144                            format!(
 145                                "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
 146                            )
 147                            .as_str(),
 148                        ))
 149                        .priority(Priority::High)
 150                        .icon(ashpd::desktop::Icon::with_names(&[
 151                            "dialog-question-symbolic",
 152                        ])),
 153                )
 154                .await
 155                .ok();
 156
 157            process::exit(1);
 158        })
 159        .detach();
 160    }
 161}
 162
 163pub fn main() {
 164    #[cfg(unix)]
 165    util::prevent_root_execution();
 166
 167    let args = Args::parse();
 168
 169    // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
 170    #[cfg(not(target_os = "windows"))]
 171    if let Some(socket) = &args.askpass {
 172        askpass::main(socket);
 173        return;
 174    }
 175
 176    // `zed --crash-handler` Makes zed operate in minidump crash handler mode
 177    if let Some(socket) = &args.crash_handler {
 178        crashes::crash_server(socket.as_path());
 179        return;
 180    }
 181
 182    // `zed --nc` Makes zed operate in nc/netcat mode for use with MCP
 183    if let Some(socket) = &args.nc {
 184        match nc::main(socket) {
 185            Ok(()) => return,
 186            Err(err) => {
 187                eprintln!("Error: {}", err);
 188                process::exit(1);
 189            }
 190        }
 191    }
 192
 193    // `zed --printenv` Outputs environment variables as JSON to stdout
 194    if args.printenv {
 195        util::shell_env::print_env();
 196        return;
 197    }
 198
 199    if args.dump_all_actions {
 200        dump_all_gpui_actions();
 201        return;
 202    }
 203
 204    // Set custom data directory.
 205    if let Some(dir) = &args.user_data_dir {
 206        paths::set_custom_data_dir(dir);
 207    }
 208
 209    #[cfg(all(not(debug_assertions), target_os = "windows"))]
 210    unsafe {
 211        use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
 212
 213        if args.foreground {
 214            let _ = AttachConsole(ATTACH_PARENT_PROCESS);
 215        }
 216    }
 217
 218    #[cfg(target_os = "windows")]
 219    match util::get_zed_cli_path() {
 220        Ok(path) => askpass::set_askpass_program(path),
 221        Err(err) => {
 222            eprintln!("Error: {}", err);
 223            if std::option_env!("ZED_BUNDLE").is_some() {
 224                process::exit(1);
 225            }
 226        }
 227    }
 228
 229    let file_errors = init_paths();
 230    if !file_errors.is_empty() {
 231        files_not_created_on_launch(file_errors);
 232        return;
 233    }
 234
 235    zlog::init();
 236    if stdout_is_a_pty() {
 237        zlog::init_output_stdout();
 238    } else {
 239        let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));
 240        if let Err(err) = result {
 241            eprintln!("Could not open log file: {}... Defaulting to stdout", err);
 242            zlog::init_output_stdout();
 243        };
 244    }
 245
 246    let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"));
 247    let app_commit_sha =
 248        option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
 249
 250    if args.system_specs {
 251        let system_specs = system_specs::SystemSpecs::new_stateless(
 252            app_version,
 253            app_commit_sha,
 254            *release_channel::RELEASE_CHANNEL,
 255        );
 256        println!("Zed System Specs (from CLI):\n{}", system_specs);
 257        return;
 258    }
 259
 260    rayon::ThreadPoolBuilder::new()
 261        .num_threads(4)
 262        .stack_size(10 * 1024 * 1024)
 263        .thread_name(|ix| format!("RayonWorker{}", ix))
 264        .build_global()
 265        .unwrap();
 266
 267    log::info!(
 268        "========== starting zed version {}, sha {} ==========",
 269        app_version,
 270        app_commit_sha
 271            .as_ref()
 272            .map(|sha| sha.short())
 273            .as_deref()
 274            .unwrap_or("unknown"),
 275    );
 276
 277    #[cfg(windows)]
 278    check_for_conpty_dll();
 279
 280    let app = Application::new().with_assets(Assets);
 281
 282    let system_id = app.background_executor().block(system_id()).ok();
 283    let installation_id = app.background_executor().block(installation_id()).ok();
 284    let session_id = Uuid::new_v4().to_string();
 285    let session = app.background_executor().block(Session::new());
 286
 287    app.background_executor()
 288        .spawn(crashes::init(InitCrashHandler {
 289            session_id: session_id.clone(),
 290            zed_version: app_version.to_string(),
 291            binary: "zed".to_string(),
 292            release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
 293            commit_sha: app_commit_sha
 294                .as_ref()
 295                .map(|sha| sha.full())
 296                .unwrap_or_else(|| "no sha".to_owned()),
 297        }))
 298        .detach();
 299
 300    let (open_listener, mut open_rx) = OpenListener::new();
 301
 302    let failed_single_instance_check = if *zed_env_vars::ZED_STATELESS
 303        || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
 304    {
 305        false
 306    } else {
 307        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 308        {
 309            crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
 310        }
 311
 312        #[cfg(target_os = "windows")]
 313        {
 314            !crate::zed::windows_only_instance::handle_single_instance(open_listener.clone(), &args)
 315        }
 316
 317        #[cfg(target_os = "macos")]
 318        {
 319            use zed::mac_only_instance::*;
 320            ensure_only_instance() != IsOnlyInstance::Yes
 321        }
 322    };
 323    if failed_single_instance_check {
 324        println!("zed is already running");
 325        return;
 326    }
 327
 328    let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
 329    let git_binary_path =
 330        if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
 331            app.path_for_auxiliary_executable("git")
 332                .context("could not find git binary path")
 333                .log_err()
 334        } else {
 335            None
 336        };
 337    log::info!("Using git binary path: {:?}", git_binary_path);
 338
 339    let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
 340    let user_settings_file_rx = watch_config_file(
 341        &app.background_executor(),
 342        fs.clone(),
 343        paths::settings_file().clone(),
 344    );
 345    let global_settings_file_rx = watch_config_file(
 346        &app.background_executor(),
 347        fs.clone(),
 348        paths::global_settings_file().clone(),
 349    );
 350    let user_keymap_file_rx = watch_config_file(
 351        &app.background_executor(),
 352        fs.clone(),
 353        paths::keymap_file().clone(),
 354    );
 355
 356    let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
 357    if !stdout_is_a_pty() {
 358        app.background_executor()
 359            .spawn(async {
 360                #[cfg(unix)]
 361                util::load_login_shell_environment().await.log_err();
 362                shell_env_loaded_tx.send(()).ok();
 363            })
 364            .detach()
 365    } else {
 366        drop(shell_env_loaded_tx)
 367    }
 368
 369    app.on_open_urls({
 370        let open_listener = open_listener.clone();
 371        move |urls| {
 372            open_listener.open(RawOpenRequest {
 373                urls,
 374                diff_paths: Vec::new(),
 375                ..Default::default()
 376            })
 377        }
 378    });
 379    app.on_reopen(move |cx| {
 380        if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
 381        {
 382            cx.spawn({
 383                let app_state = app_state;
 384                async move |cx| {
 385                    if let Err(e) = restore_or_create_workspace(app_state, cx).await {
 386                        fail_to_open_window_async(e, cx)
 387                    }
 388                }
 389            })
 390            .detach();
 391        }
 392    });
 393
 394    app.run(move |cx| {
 395        menu::init();
 396        zed_actions::init();
 397
 398        release_channel::init(app_version, cx);
 399        gpui_tokio::init(cx);
 400        if let Some(app_commit_sha) = app_commit_sha {
 401            AppCommitSha::set_global(app_commit_sha, cx);
 402        }
 403        settings::init(cx);
 404        zlog_settings::init(cx);
 405        handle_settings_file_changes(
 406            user_settings_file_rx,
 407            global_settings_file_rx,
 408            cx,
 409            handle_settings_changed,
 410        );
 411        handle_keymap_file_changes(user_keymap_file_rx, cx);
 412        client::init_settings(cx);
 413        let user_agent = format!(
 414            "Zed/{} ({}; {})",
 415            AppVersion::global(cx),
 416            std::env::consts::OS,
 417            std::env::consts::ARCH
 418        );
 419        let proxy_url = ProxySettings::get_global(cx).proxy_url();
 420        let http = {
 421            let _guard = Tokio::handle(cx).enter();
 422
 423            ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
 424                .expect("could not start HTTP client")
 425        };
 426        cx.set_http_client(Arc::new(http));
 427
 428        <dyn Fs>::set_global(fs.clone(), cx);
 429
 430        GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
 431        git_hosting_providers::init(cx);
 432
 433        OpenListener::set_global(cx, open_listener.clone());
 434
 435        extension::init(cx);
 436        let extension_host_proxy = ExtensionHostProxy::global(cx);
 437
 438        let client = Client::production(cx);
 439        cx.set_http_client(client.http_client());
 440        let mut languages = LanguageRegistry::new(cx.background_executor().clone());
 441        languages.set_language_server_download_dir(paths::languages_dir().clone());
 442        let languages = Arc::new(languages);
 443        let (mut tx, rx) = watch::channel(None);
 444        cx.observe_global::<SettingsStore>(move |cx| {
 445            let settings = &ProjectSettings::get_global(cx).node;
 446            let options = NodeBinaryOptions {
 447                allow_path_lookup: !settings.ignore_system_version,
 448                // TODO: Expose this setting
 449                allow_binary_download: true,
 450                use_paths: settings.path.as_ref().map(|node_path| {
 451                    let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
 452                    let npm_path = settings
 453                        .npm_path
 454                        .as_ref()
 455                        .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
 456                    (
 457                        node_path.clone(),
 458                        npm_path.unwrap_or_else(|| {
 459                            let base_path = PathBuf::new();
 460                            node_path.parent().unwrap_or(&base_path).join("npm")
 461                        }),
 462                    )
 463                }),
 464            };
 465            tx.send(Some(options)).log_err();
 466        })
 467        .detach();
 468        let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
 469
 470        debug_adapter_extension::init(extension_host_proxy.clone(), cx);
 471        language::init(cx);
 472        languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx);
 473        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
 474        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 475
 476        language_extension::init(
 477            language_extension::LspAccess::ViaWorkspaces({
 478                let workspace_store = workspace_store.clone();
 479                Arc::new(move |cx: &mut App| {
 480                    workspace_store.update(cx, |workspace_store, cx| {
 481                        workspace_store
 482                            .workspaces()
 483                            .iter()
 484                            .map(|workspace| {
 485                                workspace.update(cx, |workspace, _, cx| {
 486                                    workspace.project().read(cx).lsp_store()
 487                                })
 488                            })
 489                            .collect()
 490                    })
 491                })
 492            }),
 493            extension_host_proxy.clone(),
 494            languages.clone(),
 495        );
 496
 497        Client::set_global(client.clone(), cx);
 498
 499        zed::init(cx);
 500        project::Project::init(&client, cx);
 501        debugger_ui::init(cx);
 502        debugger_tools::init(cx);
 503        client::init(&client, cx);
 504        let telemetry = client.telemetry();
 505        telemetry.start(
 506            system_id.as_ref().map(|id| id.to_string()),
 507            installation_id.as_ref().map(|id| id.to_string()),
 508            session_id.clone(),
 509            cx,
 510        );
 511
 512        // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
 513        if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
 514            match (&system_id, &installation_id) {
 515                (IdType::New(_), IdType::New(_)) => {
 516                    telemetry::event!("App First Opened");
 517                    telemetry::event!("App First Opened For Release Channel");
 518                }
 519                (IdType::Existing(_), IdType::New(_)) => {
 520                    telemetry::event!("App First Opened For Release Channel");
 521                }
 522                (_, IdType::Existing(_)) => {
 523                    telemetry::event!("App Opened");
 524                }
 525            }
 526        }
 527        let app_session = cx.new(|cx| AppSession::new(session, cx));
 528
 529        let app_state = Arc::new(AppState {
 530            languages,
 531            client: client.clone(),
 532            user_store,
 533            fs: fs.clone(),
 534            build_window_options,
 535            workspace_store,
 536            node_runtime,
 537            session: app_session,
 538        });
 539        AppState::set_global(Arc::downgrade(&app_state), cx);
 540
 541        auto_update::init(client.http_client(), cx);
 542        dap_adapters::init(cx);
 543        auto_update_ui::init(cx);
 544        reliability::init(
 545            client.http_client(),
 546            system_id.as_ref().map(|id| id.to_string()),
 547            cx,
 548        );
 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        agent_settings::init(cx);
 577        acp_tools::init(cx);
 578        zeta2_tools::init(cx);
 579        web_search::init(cx);
 580        web_search_providers::init(app_state.client.clone(), cx);
 581        snippet_provider::init(cx);
 582        edit_prediction_registry::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 583        let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
 584        agent_ui::init(
 585            app_state.fs.clone(),
 586            app_state.client.clone(),
 587            prompt_builder.clone(),
 588            app_state.languages.clone(),
 589            false,
 590            cx,
 591        );
 592        repl::init(app_state.fs.clone(), cx);
 593        recent_projects::init(cx);
 594
 595        load_embedded_fonts(cx);
 596
 597        editor::init(cx);
 598        image_viewer::init(cx);
 599        repl::notebook::init(cx);
 600        diagnostics::init(cx);
 601
 602        audio::init(cx);
 603        workspace::init(app_state.clone(), cx);
 604        ui_prompt::init(cx);
 605
 606        go_to_line::init(cx);
 607        file_finder::init(cx);
 608        tab_switcher::init(cx);
 609        outline::init(cx);
 610        project_symbols::init(cx);
 611        project_panel::init(cx);
 612        outline_panel::init(cx);
 613        tasks_ui::init(cx);
 614        snippets_ui::init(cx);
 615        channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
 616        search::init(cx);
 617        vim::init(cx);
 618        terminal_view::init(cx);
 619        journal::init(app_state.clone(), cx);
 620        language_selector::init(cx);
 621        line_ending_selector::init(cx);
 622        toolchain_selector::init(cx);
 623        theme_selector::init(cx);
 624        settings_profile_selector::init(cx);
 625        language_tools::init(cx);
 626        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 627        notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 628        collab_ui::init(&app_state, cx);
 629        git_ui::init(cx);
 630        feedback::init(cx);
 631        markdown_preview::init(cx);
 632        svg_preview::init(cx);
 633        onboarding::init(cx);
 634        settings_ui::init(cx);
 635        keymap_editor::init(cx);
 636        extensions_ui::init(cx);
 637        zeta::init(cx);
 638        inspector_ui::init(app_state.clone(), cx);
 639        json_schema_store::init(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    ]
1230    .into_iter()
1231    .fold(HashMap::default(), |mut errors, path| {
1232        if let Err(e) = std::fs::create_dir_all(path) {
1233            errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
1234        }
1235        errors
1236    })
1237}
1238
1239pub fn stdout_is_a_pty() -> bool {
1240    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
1241}
1242
1243#[derive(Parser, Debug)]
1244#[command(name = "zed", disable_version_flag = true)]
1245struct Args {
1246    /// A sequence of space-separated paths or urls that you want to open.
1247    ///
1248    /// Use `path:line:row` syntax to open a file at a specific location.
1249    /// Non-existing paths and directories will ignore `:line:row` suffix.
1250    ///
1251    /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1252    paths_or_urls: Vec<String>,
1253
1254    /// Pairs of file paths to diff. Can be specified multiple times.
1255    #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
1256    diff: Vec<String>,
1257
1258    /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1259    /// This overrides the default platform-specific data directory location.
1260    /// On macOS, the default is `~/Library/Application Support/Zed`.
1261    /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
1262    /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
1263    #[arg(long, value_name = "DIR")]
1264    user_data_dir: Option<String>,
1265
1266    /// The username and WSL distribution to use when opening paths. If not specified,
1267    /// Zed will attempt to open the paths directly.
1268    ///
1269    /// The username is optional, and if not specified, the default user for the distribution
1270    /// will be used.
1271    ///
1272    /// Example: `me@Ubuntu` or `Ubuntu`.
1273    ///
1274    /// WARN: You should not fill in this field by hand.
1275    #[cfg(target_os = "windows")]
1276    #[arg(long, value_name = "USER@DISTRO")]
1277    wsl: Option<String>,
1278
1279    /// Instructs zed to run as a dev server on this machine. (not implemented)
1280    #[arg(long)]
1281    dev_server_token: Option<String>,
1282
1283    /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
1284    /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
1285    #[arg(long)]
1286    system_specs: bool,
1287
1288    /// Used for the MCP Server, to remove the need for netcat as a dependency,
1289    /// by having Zed act like netcat communicating over a Unix socket.
1290    #[arg(long, hide = true)]
1291    nc: Option<String>,
1292
1293    /// Used for recording minidumps on crashes by having Zed run a separate
1294    /// process communicating over a socket.
1295    #[arg(long, hide = true)]
1296    crash_handler: Option<PathBuf>,
1297
1298    /// Run zed in the foreground, only used on Windows, to match the behavior on macOS.
1299    #[arg(long)]
1300    #[cfg(target_os = "windows")]
1301    #[arg(hide = true)]
1302    foreground: bool,
1303
1304    /// The dock action to perform. This is used on Windows only.
1305    #[arg(long)]
1306    #[cfg(target_os = "windows")]
1307    #[arg(hide = true)]
1308    dock_action: Option<usize>,
1309
1310    /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1311    /// by having Zed act like netcat communicating over a Unix socket.
1312    #[arg(long)]
1313    #[cfg(not(target_os = "windows"))]
1314    #[arg(hide = true)]
1315    askpass: Option<String>,
1316
1317    #[arg(long, hide = true)]
1318    dump_all_actions: bool,
1319
1320    /// Output current environment variables as JSON to stdout
1321    #[arg(long, hide = true)]
1322    printenv: bool,
1323}
1324
1325#[derive(Clone, Debug)]
1326enum IdType {
1327    New(String),
1328    Existing(String),
1329}
1330
1331impl ToString for IdType {
1332    fn to_string(&self) -> String {
1333        match self {
1334            IdType::New(id) | IdType::Existing(id) => id.clone(),
1335        }
1336    }
1337}
1338
1339fn parse_url_arg(arg: &str, cx: &App) -> String {
1340    match std::fs::canonicalize(Path::new(&arg)) {
1341        Ok(path) => format!("file://{}", path.display()),
1342        Err(_) => {
1343            if arg.starts_with("file://")
1344                || arg.starts_with("zed-cli://")
1345                || arg.starts_with("ssh://")
1346                || parse_zed_link(arg, cx).is_some()
1347            {
1348                arg.into()
1349            } else {
1350                format!("file://{arg}")
1351            }
1352        }
1353    }
1354}
1355
1356fn load_embedded_fonts(cx: &App) {
1357    let asset_source = cx.asset_source();
1358    let font_paths = asset_source.list("fonts").unwrap();
1359    let embedded_fonts = Mutex::new(Vec::new());
1360    let executor = cx.background_executor();
1361
1362    executor.block(executor.scoped(|scope| {
1363        for font_path in &font_paths {
1364            if !font_path.ends_with(".ttf") {
1365                continue;
1366            }
1367
1368            scope.spawn(async {
1369                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1370                embedded_fonts.lock().push(font_bytes);
1371            });
1372        }
1373    }));
1374
1375    cx.text_system()
1376        .add_fonts(embedded_fonts.into_inner())
1377        .unwrap();
1378}
1379
1380/// Spawns a background task to load the user themes from the themes directory.
1381fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1382    cx.spawn({
1383        let fs = fs.clone();
1384        async move |cx| {
1385            if let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx)).log_err() {
1386                let themes_dir = paths::themes_dir().as_ref();
1387                match fs
1388                    .metadata(themes_dir)
1389                    .await
1390                    .ok()
1391                    .flatten()
1392                    .map(|m| m.is_dir)
1393                {
1394                    Some(is_dir) => {
1395                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1396                    }
1397                    None => {
1398                        fs.create_dir(themes_dir).await.with_context(|| {
1399                            format!("Failed to create themes dir at path {themes_dir:?}")
1400                        })?;
1401                    }
1402                }
1403                theme_registry.load_user_themes(themes_dir, fs).await?;
1404                cx.update(GlobalTheme::reload_theme)?;
1405            }
1406            anyhow::Ok(())
1407        }
1408    })
1409    .detach_and_log_err(cx);
1410}
1411
1412/// Spawns a background task to watch the themes directory for changes.
1413fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1414    use std::time::Duration;
1415    cx.spawn(async move |cx| {
1416        let (mut events, _) = fs
1417            .watch(paths::themes_dir(), Duration::from_millis(100))
1418            .await;
1419
1420        while let Some(paths) = events.next().await {
1421            for event in paths {
1422                if fs.metadata(&event.path).await.ok().flatten().is_some()
1423                    && let Some(theme_registry) =
1424                        cx.update(|cx| ThemeRegistry::global(cx)).log_err()
1425                    && let Some(()) = theme_registry
1426                        .load_user_theme(&event.path, fs.clone())
1427                        .await
1428                        .log_err()
1429                {
1430                    cx.update(GlobalTheme::reload_theme).log_err();
1431                }
1432            }
1433        }
1434    })
1435    .detach()
1436}
1437
1438#[cfg(debug_assertions)]
1439fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1440    use std::time::Duration;
1441
1442    cx.background_spawn(async move {
1443        let languages_src = Path::new("crates/languages/src");
1444        let Some(languages_src) = fs.canonicalize(languages_src).await.log_err() else {
1445            return;
1446        };
1447
1448        let (mut events, watcher) = fs.watch(&languages_src, Duration::from_millis(100)).await;
1449
1450        // add subdirectories since fs.watch is not recursive on Linux
1451        if let Some(mut paths) = fs.read_dir(&languages_src).await.log_err() {
1452            while let Some(path) = paths.next().await {
1453                if let Some(path) = path.log_err()
1454                    && fs.is_dir(&path).await
1455                {
1456                    watcher.add(&path).log_err();
1457                }
1458            }
1459        }
1460
1461        while let Some(event) = events.next().await {
1462            let has_language_file = event
1463                .iter()
1464                .any(|event| event.path.extension().is_some_and(|ext| ext == "scm"));
1465            if has_language_file {
1466                languages.reload();
1467            }
1468        }
1469    })
1470    .detach();
1471}
1472
1473#[cfg(not(debug_assertions))]
1474fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}
1475
1476fn dump_all_gpui_actions() {
1477    #[derive(Debug, serde::Serialize)]
1478    struct ActionDef {
1479        name: &'static str,
1480        human_name: String,
1481        aliases: &'static [&'static str],
1482        documentation: Option<&'static str>,
1483    }
1484    let mut actions = gpui::generate_list_of_all_registered_actions()
1485        .map(|action| ActionDef {
1486            name: action.name,
1487            human_name: command_palette::humanize_action_name(action.name),
1488            aliases: action.deprecated_aliases,
1489            documentation: action.documentation,
1490        })
1491        .collect::<Vec<ActionDef>>();
1492
1493    actions.sort_by_key(|a| a.name);
1494
1495    io::Write::write(
1496        &mut std::io::stdout(),
1497        serde_json::to_string_pretty(&actions).unwrap().as_bytes(),
1498    )
1499    .unwrap();
1500}
1501
1502#[cfg(target_os = "windows")]
1503fn check_for_conpty_dll() {
1504    use windows::{
1505        Win32::{Foundation::FreeLibrary, System::LibraryLoader::LoadLibraryW},
1506        core::w,
1507    };
1508
1509    if let Ok(hmodule) = unsafe { LoadLibraryW(w!("conpty.dll")) } {
1510        unsafe {
1511            FreeLibrary(hmodule)
1512                .context("Failed to free conpty.dll")
1513                .log_err();
1514        }
1515    } else {
1516        log::warn!("Failed to load conpty.dll. Terminal will work with reduced functionality.");
1517    }
1518}