main.rs

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