Separate out component_preview crate and add easy-to-use example binaries (#45382)

Mikayla Maki created

Release Notes:

- N/A

Change summary

Cargo.lock                                                |  29 +
Cargo.toml                                                |   2 
crates/component_preview/Cargo.toml                       |  45 ++
crates/component_preview/LICENSE-GPL                      |   1 
crates/component_preview/examples/component_preview.rs    |  18 +
crates/component_preview/src/component_preview.rs         |  14 
crates/component_preview/src/component_preview_example.rs | 145 +++++++++
crates/component_preview/src/persistence.rs               |   0 
crates/zed/Cargo.toml                                     |   2 
crates/zed/src/main.rs                                    |   2 
crates/zed/src/zed.rs                                     |   1 
11 files changed, 248 insertions(+), 11 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3525,6 +3525,33 @@ dependencies = [
  "theme",
 ]
 
+[[package]]
+name = "component_preview"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client",
+ "collections",
+ "component",
+ "db",
+ "fs",
+ "gpui",
+ "language",
+ "log",
+ "node_runtime",
+ "notifications",
+ "project",
+ "release_channel",
+ "reqwest_client",
+ "session",
+ "settings",
+ "theme",
+ "ui",
+ "ui_input",
+ "uuid",
+ "workspace",
+]
+
 [[package]]
 name = "compression-codecs"
 version = "0.4.31"
@@ -20643,6 +20670,7 @@ dependencies = [
  "collections",
  "command_palette",
  "component",
+ "component_preview",
  "copilot",
  "crashes",
  "dap",
@@ -20748,7 +20776,6 @@ dependencies = [
  "tree-sitter-md",
  "tree-sitter-rust",
  "ui",
- "ui_input",
  "ui_prompt",
  "url",
  "urlencoding",

Cargo.toml 🔗

@@ -39,6 +39,7 @@ members = [
     "crates/command_palette",
     "crates/command_palette_hooks",
     "crates/component",
+    "crates/component_preview",
     "crates/context_server",
     "crates/copilot",
     "crates/crashes",
@@ -275,6 +276,7 @@ collections = { path = "crates/collections", version = "0.1.0" }
 command_palette = { path = "crates/command_palette" }
 command_palette_hooks = { path = "crates/command_palette_hooks" }
 component = { path = "crates/component" }
+component_preview  = { path = "crates/component_preview" }
 context_server = { path = "crates/context_server" }
 copilot = { path = "crates/copilot" }
 crashes = { path = "crates/crashes" }

crates/component_preview/Cargo.toml 🔗

@@ -0,0 +1,45 @@
+[package]
+name = "component_preview"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/component_preview.rs"
+
+[features]
+default = []
+preview = []
+test-support = ["db/test-support"]
+
+[dependencies]
+anyhow.workspace = true
+client.workspace = true
+collections.workspace = true
+component.workspace = true
+db.workspace = true
+fs.workspace = true
+gpui.workspace = true
+language.workspace = true
+log.workspace = true
+node_runtime.workspace = true
+notifications.workspace = true
+project.workspace = true
+release_channel.workspace = true
+reqwest_client.workspace = true
+session.workspace = true
+settings.workspace = true
+theme.workspace = true
+ui.workspace = true
+ui_input.workspace = true
+uuid.workspace = true
+workspace.workspace = true
+
+[[example]]
+name = "component_preview"
+path = "examples/component_preview.rs"
+required-features = ["preview"]

crates/component_preview/examples/component_preview.rs 🔗

@@ -0,0 +1,18 @@
+//! Component Preview Example
+//!
+//! Run with: `cargo run -p component_preview --example component_preview --features="preview"`
+//!
+//! To use this in other projects, add the following to your `Cargo.toml`:
+//!
+//! ```toml
+//! [dependencies]
+//! component_preview = { path = "../component_preview", features = ["preview"] }
+//!
+//! [[example]]
+//! name = "component_preview"
+//! path = "examples/component_preview.rs"
+//! ```
+
+fn main() {
+    component_preview::run_component_preview();
+}

crates/zed/src/zed/component_preview.rs → crates/component_preview/src/component_preview.rs 🔗

@@ -1,7 +1,4 @@
-//! # Component Preview
-//!
-//! A view for exploring Zed components.
-
+mod component_preview_example;
 mod persistence;
 
 use client::UserStore;
@@ -11,18 +8,21 @@ use gpui::{
     App, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, Window, list, prelude::*,
 };
 use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle};
-use languages::LanguageRegistry;
+use language::LanguageRegistry;
 use notifications::status_toast::{StatusToast, ToastIcon};
 use persistence::COMPONENT_PREVIEW_DB;
 use project::Project;
 use std::{iter::Iterator, ops::Range, sync::Arc};
 use ui::{ButtonLike, Divider, HighlightedLabel, ListItem, ListSubHeader, Tooltip, prelude::*};
 use ui_input::InputField;
+use workspace::AppState;
 use workspace::{
-    AppState, Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items,
-    item::ItemEvent,
+    Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items, item::ItemEvent,
 };
 
+#[allow(unused_imports)]
+pub use component_preview_example::*;
+
 pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     workspace::register_serializable_item::<ComponentPreview>(cx);
 

crates/component_preview/src/component_preview_example.rs 🔗

@@ -0,0 +1,145 @@
+/// Run the component preview application.
+///
+/// This initializes the application with minimal required infrastructure
+/// and opens a workspace with the ComponentPreview item.
+#[cfg(feature = "preview")]
+pub fn run_component_preview() {
+    use fs::RealFs;
+    use gpui::{
+        AppContext as _, Application, Bounds, KeyBinding, WindowBounds, WindowOptions, actions,
+        size,
+    };
+
+    use client::{Client, UserStore};
+    use language::LanguageRegistry;
+    use node_runtime::NodeRuntime;
+    use project::Project;
+    use reqwest_client::ReqwestClient;
+    use session::{AppSession, Session};
+    use std::sync::Arc;
+    use ui::{App, px};
+    use workspace::{AppState, Workspace, WorkspaceStore};
+
+    use crate::{ComponentPreview, init};
+
+    actions!(zed, [Quit]);
+
+    fn quit(_: &Quit, cx: &mut App) {
+        cx.quit();
+    }
+
+    Application::new().run(|cx| {
+        component::init();
+
+        cx.on_action(quit);
+        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
+        let version = release_channel::AppVersion::load(env!("CARGO_PKG_VERSION"), None, None);
+        release_channel::init(version, cx);
+
+        let http_client =
+            ReqwestClient::user_agent("component_preview").expect("Failed to create HTTP client");
+        cx.set_http_client(Arc::new(http_client));
+
+        let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
+        <dyn fs::Fs>::set_global(fs.clone(), cx);
+
+        settings::init(cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
+
+        let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
+        let client = Client::production(cx);
+        client::init(&client, cx);
+
+        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
+        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
+        let session_id = uuid::Uuid::new_v4().to_string();
+        let session = cx.background_executor().block(Session::new(session_id));
+        let session = cx.new(|cx| AppSession::new(session, cx));
+        let node_runtime = NodeRuntime::unavailable();
+
+        let app_state = Arc::new(AppState {
+            languages,
+            client,
+            user_store,
+            workspace_store,
+            fs,
+            build_window_options: |_, _| Default::default(),
+            node_runtime,
+            session,
+        });
+        AppState::set_global(Arc::downgrade(&app_state), cx);
+
+        workspace::init(app_state.clone(), cx);
+        init(app_state.clone(), cx);
+
+        let size = size(px(1200.), px(800.));
+        let bounds = Bounds::centered(None, size, cx);
+
+        cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(bounds)),
+                ..Default::default()
+            },
+            {
+                move |window, cx| {
+                    let app_state = app_state;
+                    theme::setup_ui_font(window, cx);
+
+                    let project = Project::local(
+                        app_state.client.clone(),
+                        app_state.node_runtime.clone(),
+                        app_state.user_store.clone(),
+                        app_state.languages.clone(),
+                        app_state.fs.clone(),
+                        None,
+                        false,
+                        cx,
+                    );
+
+                    let workspace = cx.new(|cx| {
+                        Workspace::new(
+                            Default::default(),
+                            project.clone(),
+                            app_state.clone(),
+                            window,
+                            cx,
+                        )
+                    });
+
+                    workspace.update(cx, |workspace, cx| {
+                        let weak_workspace = cx.entity().downgrade();
+                        let language_registry = app_state.languages.clone();
+                        let user_store = app_state.user_store.clone();
+
+                        let component_preview = cx.new(|cx| {
+                            ComponentPreview::new(
+                                weak_workspace,
+                                project,
+                                language_registry,
+                                user_store,
+                                None,
+                                None,
+                                window,
+                                cx,
+                            )
+                            .expect("Failed to create component preview")
+                        });
+
+                        workspace.add_item_to_active_pane(
+                            Box::new(component_preview),
+                            None,
+                            true,
+                            window,
+                            cx,
+                        );
+                    });
+
+                    workspace
+                }
+            },
+        )
+        .expect("Failed to open component preview window");
+
+        cx.activate(true);
+    });
+}

crates/zed/Cargo.toml 🔗

@@ -41,6 +41,7 @@ collab_ui.workspace = true
 collections.workspace = true
 command_palette.workspace = true
 component.workspace = true
+component_preview.workspace = true
 copilot.workspace = true
 crashes.workspace = true
 dap_adapters.workspace = true
@@ -148,7 +149,6 @@ ztracing.workspace = true
 tracing.workspace = true
 toolchain_selector.workspace = true
 ui.workspace = true
-ui_input.workspace = true
 ui_prompt.workspace = true
 url.workspace = true
 urlencoding.workspace = true

crates/zed/src/main.rs 🔗

@@ -774,7 +774,7 @@ fn main() {
 
         let app_state = app_state.clone();
 
-        crate::zed::component_preview::init(app_state.clone(), cx);
+        component_preview::init(app_state.clone(), cx);
 
         cx.spawn(async move |cx| {
             while let Some(urls) = open_rx.next().await {

crates/zed/src/zed.rs 🔗

@@ -1,5 +1,4 @@
 mod app_menus;
-pub mod component_preview;
 pub mod edit_prediction_registry;
 #[cfg(target_os = "macos")]
 pub(crate) mod mac_only_instance;