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