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        repl::init(app_state.fs.clone(), cx);
 586        recent_projects::init(cx);
 587
 588        load_embedded_fonts(cx);
 589
 590        editor::init(cx);
 591        image_viewer::init(cx);
 592        repl::notebook::init(cx);
 593        diagnostics::init(cx);
 594
 595        audio::init(cx);
 596        workspace::init(app_state.clone(), cx);
 597        ui_prompt::init(cx);
 598
 599        go_to_line::init(cx);
 600        file_finder::init(cx);
 601        tab_switcher::init(cx);
 602        outline::init(cx);
 603        project_symbols::init(cx);
 604        project_panel::init(cx);
 605        outline_panel::init(cx);
 606        tasks_ui::init(cx);
 607        snippets_ui::init(cx);
 608        channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
 609        search::init(cx);
 610        vim::init(cx);
 611        terminal_view::init(cx);
 612        journal::init(app_state.clone(), cx);
 613        language_selector::init(cx);
 614        line_ending_selector::init(cx);
 615        toolchain_selector::init(cx);
 616        theme_selector::init(cx);
 617        settings_profile_selector::init(cx);
 618        language_tools::init(cx);
 619        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 620        notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 621        collab_ui::init(&app_state, cx);
 622        git_ui::init(cx);
 623        feedback::init(cx);
 624        markdown_preview::init(cx);
 625        svg_preview::init(cx);
 626        onboarding::init(cx);
 627        settings_ui::init(cx);
 628        keymap_editor::init(cx);
 629        extensions_ui::init(cx);
 630        zeta::init(cx);
 631        inspector_ui::init(app_state.clone(), cx);
 632        json_schema_store::init(cx);
 633        encodings_ui::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            OpenRequestKind::Setting { setting_path } => {
 852                // zed://settings/languages/$(language)/tab_size  - DONT SUPPORT
 853                // zed://settings/languages/Rust/tab_size  - SUPPORT
 854                // languages.$(language).tab_size
 855                // [ languages $(language) tab_size]
 856                workspace::with_active_or_new_workspace(cx, |_workspace, window, cx| {
 857                    match setting_path {
 858                        None => window.dispatch_action(Box::new(zed_actions::OpenSettings), cx),
 859                        Some(setting_path) => window.dispatch_action(
 860                            Box::new(zed_actions::OpenSettingsAt { path: setting_path }),
 861                            cx,
 862                        ),
 863                    }
 864                });
 865            }
 866        }
 867
 868        return;
 869    }
 870
 871    if let Some(connection_options) = request.remote_connection {
 872        cx.spawn(async move |cx| {
 873            let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
 874            open_remote_project(
 875                connection_options,
 876                paths,
 877                app_state,
 878                workspace::OpenOptions::default(),
 879                cx,
 880            )
 881            .await
 882        })
 883        .detach_and_log_err(cx);
 884        return;
 885    }
 886
 887    let mut task = None;
 888    if !request.open_paths.is_empty() || !request.diff_paths.is_empty() {
 889        let app_state = app_state.clone();
 890        task = Some(cx.spawn(async move |cx| {
 891            let paths_with_position =
 892                derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
 893            let (_window, results) = open_paths_with_positions(
 894                &paths_with_position,
 895                &request.diff_paths,
 896                app_state,
 897                workspace::OpenOptions::default(),
 898                cx,
 899            )
 900            .await?;
 901            for result in results.into_iter().flatten() {
 902                if let Err(err) = result {
 903                    log::error!("Error opening path: {err}",);
 904                }
 905            }
 906            anyhow::Ok(())
 907        }));
 908    }
 909
 910    if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
 911        cx.spawn(async move |cx| {
 912            let result = maybe!(async {
 913                if let Some(task) = task {
 914                    task.await?;
 915                }
 916                let client = app_state.client.clone();
 917                // we continue even if authentication fails as join_channel/ open channel notes will
 918                // show a visible error message.
 919                authenticate(client, cx).await.log_err();
 920
 921                if let Some(channel_id) = request.join_channel {
 922                    cx.update(|cx| {
 923                        workspace::join_channel(
 924                            client::ChannelId(channel_id),
 925                            app_state.clone(),
 926                            None,
 927                            cx,
 928                        )
 929                    })?
 930                    .await?;
 931                }
 932
 933                let workspace_window =
 934                    workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 935                let workspace = workspace_window.entity(cx)?;
 936
 937                let mut promises = Vec::new();
 938                for (channel_id, heading) in request.open_channel_notes {
 939                    promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
 940                        ChannelView::open(
 941                            client::ChannelId(channel_id),
 942                            heading,
 943                            workspace.clone(),
 944                            window,
 945                            cx,
 946                        )
 947                        .log_err()
 948                    })?)
 949                }
 950                future::join_all(promises).await;
 951                anyhow::Ok(())
 952            })
 953            .await;
 954            if let Err(err) = result {
 955                fail_to_open_window_async(err, cx);
 956            }
 957        })
 958        .detach()
 959    } else if let Some(task) = task {
 960        cx.spawn(async move |cx| {
 961            if let Err(err) = task.await {
 962                fail_to_open_window_async(err, cx);
 963            }
 964        })
 965        .detach();
 966    }
 967}
 968
 969async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> Result<()> {
 970    if stdout_is_a_pty() {
 971        if client::IMPERSONATE_LOGIN.is_some() {
 972            client.sign_in_with_optional_connect(false, cx).await?;
 973        } else if client.has_credentials(cx).await {
 974            client.sign_in_with_optional_connect(true, cx).await?;
 975        }
 976    } else if client.has_credentials(cx).await {
 977        client.sign_in_with_optional_connect(true, cx).await?;
 978    }
 979
 980    Ok(())
 981}
 982
 983async fn system_id() -> Result<IdType> {
 984    let key_name = "system_id".to_string();
 985
 986    if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
 987        return Ok(IdType::Existing(system_id));
 988    }
 989
 990    let system_id = Uuid::new_v4().to_string();
 991
 992    GLOBAL_KEY_VALUE_STORE
 993        .write_kvp(key_name, system_id.clone())
 994        .await?;
 995
 996    Ok(IdType::New(system_id))
 997}
 998
 999async fn installation_id() -> Result<IdType> {
1000    let legacy_key_name = "device_id".to_string();
1001    let key_name = "installation_id".to_string();
1002
1003    // Migrate legacy key to new key
1004    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
1005        KEY_VALUE_STORE
1006            .write_kvp(key_name, installation_id.clone())
1007            .await?;
1008        KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
1009        return Ok(IdType::Existing(installation_id));
1010    }
1011
1012    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
1013        return Ok(IdType::Existing(installation_id));
1014    }
1015
1016    let installation_id = Uuid::new_v4().to_string();
1017
1018    KEY_VALUE_STORE
1019        .write_kvp(key_name, installation_id.clone())
1020        .await?;
1021
1022    Ok(IdType::New(installation_id))
1023}
1024
1025async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
1026    if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
1027        let use_system_window_tabs = cx
1028            .update(|cx| WorkspaceSettings::get_global(cx).use_system_window_tabs)
1029            .unwrap_or(false);
1030        let mut results: Vec<Result<(), Error>> = Vec::new();
1031        let mut tasks = Vec::new();
1032
1033        for (index, (location, paths)) in locations.into_iter().enumerate() {
1034            match location {
1035                SerializedWorkspaceLocation::Local => {
1036                    let app_state = app_state.clone();
1037                    let task = cx.spawn(async move |cx| {
1038                        let open_task = cx.update(|cx| {
1039                            workspace::open_paths(
1040                                &paths.paths(),
1041                                app_state,
1042                                workspace::OpenOptions::default(),
1043                                cx,
1044                            )
1045                        })?;
1046                        open_task.await.map(|_| ())
1047                    });
1048
1049                    // If we're using system window tabs and this is the first workspace,
1050                    // wait for it to finish so that the other windows can be added as tabs.
1051                    if use_system_window_tabs && index == 0 {
1052                        results.push(task.await);
1053                    } else {
1054                        tasks.push(task);
1055                    }
1056                }
1057                SerializedWorkspaceLocation::Remote(mut connection_options) => {
1058                    let app_state = app_state.clone();
1059                    if let RemoteConnectionOptions::Ssh(options) = &mut connection_options {
1060                        cx.update(|cx| {
1061                            SshSettings::get_global(cx)
1062                                .fill_connection_options_from_settings(options)
1063                        })?;
1064                    }
1065                    let task = cx.spawn(async move |cx| {
1066                        recent_projects::open_remote_project(
1067                            connection_options,
1068                            paths.paths().into_iter().map(PathBuf::from).collect(),
1069                            app_state,
1070                            workspace::OpenOptions::default(),
1071                            cx,
1072                        )
1073                        .await
1074                        .map_err(|e| anyhow::anyhow!(e))
1075                    });
1076                    tasks.push(task);
1077                }
1078            }
1079        }
1080
1081        // Wait for all workspaces to open concurrently
1082        results.extend(future::join_all(tasks).await);
1083
1084        // Show notifications for any errors that occurred
1085        let mut error_count = 0;
1086        for result in results {
1087            if let Err(e) = result {
1088                log::error!("Failed to restore workspace: {}", e);
1089                error_count += 1;
1090            }
1091        }
1092
1093        if error_count > 0 {
1094            let message = if error_count == 1 {
1095                "Failed to restore 1 workspace. Check logs for details.".to_string()
1096            } else {
1097                format!(
1098                    "Failed to restore {} workspaces. Check logs for details.",
1099                    error_count
1100                )
1101            };
1102
1103            // Try to find an active workspace to show the toast
1104            let toast_shown = cx
1105                .update(|cx| {
1106                    if let Some(window) = cx.active_window()
1107                        && let Some(workspace) = window.downcast::<Workspace>()
1108                    {
1109                        workspace
1110                            .update(cx, |workspace, _, cx| {
1111                                workspace.show_toast(
1112                                    Toast::new(NotificationId::unique::<()>(), message),
1113                                    cx,
1114                                )
1115                            })
1116                            .ok();
1117                        return true;
1118                    }
1119                    false
1120                })
1121                .unwrap_or(false);
1122
1123            // If we couldn't show a toast (no windows opened successfully),
1124            // we've already logged the errors above, so the user can check logs
1125            if !toast_shown {
1126                log::error!(
1127                    "Failed to show notification for window restoration errors, because no workspace windows were available."
1128                );
1129            }
1130        }
1131    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
1132        cx.update(|cx| show_onboarding_view(app_state, cx))?.await?;
1133    } else {
1134        cx.update(|cx| {
1135            workspace::open_new(
1136                Default::default(),
1137                app_state,
1138                cx,
1139                |workspace, window, cx| {
1140                    Editor::new_file(workspace, &Default::default(), window, cx)
1141                },
1142            )
1143        })?
1144        .await?;
1145    }
1146
1147    Ok(())
1148}
1149
1150pub(crate) async fn restorable_workspace_locations(
1151    cx: &mut AsyncApp,
1152    app_state: &Arc<AppState>,
1153) -> Option<Vec<(SerializedWorkspaceLocation, PathList)>> {
1154    let mut restore_behavior = cx
1155        .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
1156        .ok()?;
1157
1158    let session_handle = app_state.session.clone();
1159    let (last_session_id, last_session_window_stack) = cx
1160        .update(|cx| {
1161            let session = session_handle.read(cx);
1162
1163            (
1164                session.last_session_id().map(|id| id.to_string()),
1165                session.last_session_window_stack(),
1166            )
1167        })
1168        .ok()?;
1169
1170    if last_session_id.is_none()
1171        && matches!(
1172            restore_behavior,
1173            workspace::RestoreOnStartupBehavior::LastSession
1174        )
1175    {
1176        restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
1177    }
1178
1179    match restore_behavior {
1180        workspace::RestoreOnStartupBehavior::LastWorkspace => {
1181            workspace::last_opened_workspace_location()
1182                .await
1183                .map(|location| vec![location])
1184        }
1185        workspace::RestoreOnStartupBehavior::LastSession => {
1186            if let Some(last_session_id) = last_session_id {
1187                let ordered = last_session_window_stack.is_some();
1188
1189                let mut locations = workspace::last_session_workspace_locations(
1190                    &last_session_id,
1191                    last_session_window_stack,
1192                )
1193                .filter(|locations| !locations.is_empty());
1194
1195                // Since last_session_window_order returns the windows ordered front-to-back
1196                // we need to open the window that was frontmost last.
1197                if ordered && let Some(locations) = locations.as_mut() {
1198                    locations.reverse();
1199                }
1200
1201                locations
1202            } else {
1203                None
1204            }
1205        }
1206        _ => None,
1207    }
1208}
1209
1210fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
1211    [
1212        paths::config_dir(),
1213        paths::extensions_dir(),
1214        paths::languages_dir(),
1215        paths::debug_adapters_dir(),
1216        paths::database_dir(),
1217        paths::logs_dir(),
1218        paths::temp_dir(),
1219    ]
1220    .into_iter()
1221    .fold(HashMap::default(), |mut errors, path| {
1222        if let Err(e) = std::fs::create_dir_all(path) {
1223            errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
1224        }
1225        errors
1226    })
1227}
1228
1229pub fn stdout_is_a_pty() -> bool {
1230    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
1231}
1232
1233#[derive(Parser, Debug)]
1234#[command(name = "zed", disable_version_flag = true)]
1235struct Args {
1236    /// A sequence of space-separated paths or urls that you want to open.
1237    ///
1238    /// Use `path:line:row` syntax to open a file at a specific location.
1239    /// Non-existing paths and directories will ignore `:line:row` suffix.
1240    ///
1241    /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1242    paths_or_urls: Vec<String>,
1243
1244    /// Pairs of file paths to diff. Can be specified multiple times.
1245    #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
1246    diff: Vec<String>,
1247
1248    /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1249    /// This overrides the default platform-specific data directory location.
1250    /// On macOS, the default is `~/Library/Application Support/Zed`.
1251    /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
1252    /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
1253    #[arg(long, value_name = "DIR")]
1254    user_data_dir: Option<String>,
1255
1256    /// The username and WSL distribution to use when opening paths. If not specified,
1257    /// Zed will attempt to open the paths directly.
1258    ///
1259    /// The username is optional, and if not specified, the default user for the distribution
1260    /// will be used.
1261    ///
1262    /// Example: `me@Ubuntu` or `Ubuntu`.
1263    ///
1264    /// WARN: You should not fill in this field by hand.
1265    #[cfg(target_os = "windows")]
1266    #[arg(long, value_name = "USER@DISTRO")]
1267    wsl: Option<String>,
1268
1269    /// Instructs zed to run as a dev server on this machine. (not implemented)
1270    #[arg(long)]
1271    dev_server_token: Option<String>,
1272
1273    /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
1274    /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
1275    #[arg(long)]
1276    system_specs: bool,
1277
1278    /// Used for the MCP Server, to remove the need for netcat as a dependency,
1279    /// by having Zed act like netcat communicating over a Unix socket.
1280    #[arg(long, hide = true)]
1281    nc: Option<String>,
1282
1283    /// Used for recording minidumps on crashes by having Zed run a separate
1284    /// process communicating over a socket.
1285    #[arg(long, hide = true)]
1286    crash_handler: Option<PathBuf>,
1287
1288    /// Run zed in the foreground, only used on Windows, to match the behavior on macOS.
1289    #[arg(long)]
1290    #[cfg(target_os = "windows")]
1291    #[arg(hide = true)]
1292    foreground: bool,
1293
1294    /// The dock action to perform. This is used on Windows only.
1295    #[arg(long)]
1296    #[cfg(target_os = "windows")]
1297    #[arg(hide = true)]
1298    dock_action: Option<usize>,
1299
1300    /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1301    /// by having Zed act like netcat communicating over a Unix socket.
1302    #[arg(long)]
1303    #[cfg(not(target_os = "windows"))]
1304    #[arg(hide = true)]
1305    askpass: Option<String>,
1306
1307    #[arg(long, hide = true)]
1308    dump_all_actions: bool,
1309
1310    /// Output current environment variables as JSON to stdout
1311    #[arg(long, hide = true)]
1312    printenv: bool,
1313}
1314
1315#[derive(Clone, Debug)]
1316enum IdType {
1317    New(String),
1318    Existing(String),
1319}
1320
1321impl ToString for IdType {
1322    fn to_string(&self) -> String {
1323        match self {
1324            IdType::New(id) | IdType::Existing(id) => id.clone(),
1325        }
1326    }
1327}
1328
1329fn parse_url_arg(arg: &str, cx: &App) -> String {
1330    match std::fs::canonicalize(Path::new(&arg)) {
1331        Ok(path) => format!("file://{}", path.display()),
1332        Err(_) => {
1333            if arg.starts_with("file://")
1334                || arg.starts_with("zed-cli://")
1335                || arg.starts_with("ssh://")
1336                || parse_zed_link(arg, cx).is_some()
1337            {
1338                arg.into()
1339            } else {
1340                format!("file://{arg}")
1341            }
1342        }
1343    }
1344}
1345
1346fn load_embedded_fonts(cx: &App) {
1347    let asset_source = cx.asset_source();
1348    let font_paths = asset_source.list("fonts").unwrap();
1349    let embedded_fonts = Mutex::new(Vec::new());
1350    let executor = cx.background_executor();
1351
1352    executor.block(executor.scoped(|scope| {
1353        for font_path in &font_paths {
1354            if !font_path.ends_with(".ttf") {
1355                continue;
1356            }
1357
1358            scope.spawn(async {
1359                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1360                embedded_fonts.lock().push(font_bytes);
1361            });
1362        }
1363    }));
1364
1365    cx.text_system()
1366        .add_fonts(embedded_fonts.into_inner())
1367        .unwrap();
1368}
1369
1370/// Spawns a background task to load the user themes from the themes directory.
1371fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1372    cx.spawn({
1373        let fs = fs.clone();
1374        async move |cx| {
1375            if let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx)).log_err() {
1376                let themes_dir = paths::themes_dir().as_ref();
1377                match fs
1378                    .metadata(themes_dir)
1379                    .await
1380                    .ok()
1381                    .flatten()
1382                    .map(|m| m.is_dir)
1383                {
1384                    Some(is_dir) => {
1385                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1386                    }
1387                    None => {
1388                        fs.create_dir(themes_dir).await.with_context(|| {
1389                            format!("Failed to create themes dir at path {themes_dir:?}")
1390                        })?;
1391                    }
1392                }
1393                theme_registry.load_user_themes(themes_dir, fs).await?;
1394                cx.update(GlobalTheme::reload_theme)?;
1395            }
1396            anyhow::Ok(())
1397        }
1398    })
1399    .detach_and_log_err(cx);
1400}
1401
1402/// Spawns a background task to watch the themes directory for changes.
1403fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1404    use std::time::Duration;
1405    cx.spawn(async move |cx| {
1406        let (mut events, _) = fs
1407            .watch(paths::themes_dir(), Duration::from_millis(100))
1408            .await;
1409
1410        while let Some(paths) = events.next().await {
1411            for event in paths {
1412                if fs.metadata(&event.path).await.ok().flatten().is_some()
1413                    && let Some(theme_registry) =
1414                        cx.update(|cx| ThemeRegistry::global(cx)).log_err()
1415                    && let Some(()) = theme_registry
1416                        .load_user_theme(&event.path, fs.clone())
1417                        .await
1418                        .log_err()
1419                {
1420                    cx.update(GlobalTheme::reload_theme).log_err();
1421                }
1422            }
1423        }
1424    })
1425    .detach()
1426}
1427
1428#[cfg(debug_assertions)]
1429fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1430    use std::time::Duration;
1431
1432    cx.background_spawn(async move {
1433        let languages_src = Path::new("crates/languages/src");
1434        let Some(languages_src) = fs.canonicalize(languages_src).await.log_err() else {
1435            return;
1436        };
1437
1438        let (mut events, watcher) = fs.watch(&languages_src, Duration::from_millis(100)).await;
1439
1440        // add subdirectories since fs.watch is not recursive on Linux
1441        if let Some(mut paths) = fs.read_dir(&languages_src).await.log_err() {
1442            while let Some(path) = paths.next().await {
1443                if let Some(path) = path.log_err()
1444                    && fs.is_dir(&path).await
1445                {
1446                    watcher.add(&path).log_err();
1447                }
1448            }
1449        }
1450
1451        while let Some(event) = events.next().await {
1452            let has_language_file = event
1453                .iter()
1454                .any(|event| event.path.extension().is_some_and(|ext| ext == "scm"));
1455            if has_language_file {
1456                languages.reload();
1457            }
1458        }
1459    })
1460    .detach();
1461}
1462
1463#[cfg(not(debug_assertions))]
1464fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}
1465
1466fn dump_all_gpui_actions() {
1467    #[derive(Debug, serde::Serialize)]
1468    struct ActionDef {
1469        name: &'static str,
1470        human_name: String,
1471        aliases: &'static [&'static str],
1472        documentation: Option<&'static str>,
1473    }
1474    let mut actions = gpui::generate_list_of_all_registered_actions()
1475        .map(|action| ActionDef {
1476            name: action.name,
1477            human_name: command_palette::humanize_action_name(action.name),
1478            aliases: action.deprecated_aliases,
1479            documentation: action.documentation,
1480        })
1481        .collect::<Vec<ActionDef>>();
1482
1483    actions.sort_by_key(|a| a.name);
1484
1485    io::Write::write(
1486        &mut std::io::stdout(),
1487        serde_json::to_string_pretty(&actions).unwrap().as_bytes(),
1488    )
1489    .unwrap();
1490}
1491
1492#[cfg(target_os = "windows")]
1493fn check_for_conpty_dll() {
1494    use windows::{
1495        Win32::{Foundation::FreeLibrary, System::LibraryLoader::LoadLibraryW},
1496        core::w,
1497    };
1498
1499    if let Ok(hmodule) = unsafe { LoadLibraryW(w!("conpty.dll")) } {
1500        unsafe {
1501            FreeLibrary(hmodule)
1502                .context("Failed to free conpty.dll")
1503                .log_err();
1504        }
1505    } else {
1506        log::warn!("Failed to load conpty.dll. Terminal will work with reduced functionality.");
1507    }
1508}