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