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