Add EditorInfo: zero-wiring View over shared editor entity

Mikayla Maki created

EditorInfo is a View that reads from the same ExampleEditor entity as
ExampleInput, demonstrating two completely different renderings of one
entity with automatic caching and no manual wiring:

- Returns the editor from entity() โ€” that's the entire reactive hookup
- Reads editor.content, cursor, focus_handle in render()
- Caches automatically: typing updates it, but pressing Enter (flash
  border on input) leaves it cached since the editor didn't change
- 46 lines total, no observers, no subscriptions, no cx.notify()

The parent shares the editor handle by reading it from ExampleInputState:
  let input_editor = input_state.read(cx).editor.clone();
  EditorInfo::new(input_editor)

Change summary

crates/gpui/examples/view_example/example_editor_info.rs | 46 ++++++++++
crates/gpui/examples/view_example/example_input.rs       |  2 
crates/gpui/examples/view_example/view_example_main.rs   | 39 ++++---
3 files changed, 68 insertions(+), 19 deletions(-)

Detailed changes

crates/gpui/examples/view_example/example_editor_info.rs ๐Ÿ”—

@@ -0,0 +1,46 @@
+//! `EditorInfo` โ€” a read-only View over an `ExampleEditor` entity.
+//!
+//! Demonstrates zero-wiring reactivity: just return the entity from `entity()`,
+//! read from it in `render()`, and caching + invalidation happen automatically.
+//! No observers, no subscriptions, no manual `cx.notify()`.
+
+use gpui::{App, Entity, IntoViewElement, Window, div, hsla, prelude::*, px};
+
+use crate::example_editor::ExampleEditor;
+
+#[derive(Hash, IntoViewElement)]
+pub struct EditorInfo {
+    editor: Entity<ExampleEditor>,
+}
+
+impl EditorInfo {
+    pub fn new(editor: Entity<ExampleEditor>) -> Self {
+        Self { editor }
+    }
+}
+
+impl gpui::View for EditorInfo {
+    type Entity = ExampleEditor;
+
+    fn entity(&self) -> Option<Entity<ExampleEditor>> {
+        Some(self.editor.clone())
+    }
+
+    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+        let editor = self.editor.read(cx);
+        let char_count = editor.content.len();
+        let cursor = editor.cursor;
+        let is_focused = editor.focus_handle.is_focused(window);
+
+        div()
+            .flex()
+            .gap(px(8.))
+            .text_xs()
+            .text_color(hsla(0., 0., 0.45, 1.))
+            .child(format!("{char_count} chars"))
+            .child("ยท")
+            .child(format!("cursor {cursor}"))
+            .child("ยท")
+            .child(if is_focused { "focused" } else { "unfocused" })
+    }
+}

crates/gpui/examples/view_example/example_input.rs ๐Ÿ”—

@@ -17,7 +17,7 @@ use crate::example_editor::ExampleEditor;
 use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
 
 pub struct ExampleInputState {
-    editor: Entity<ExampleEditor>,
+    pub editor: Entity<ExampleEditor>,
     focus_handle: FocusHandle,
     is_focused: bool,
     flash_count: usize,

crates/gpui/examples/view_example/view_example_main.rs ๐Ÿ”—

@@ -7,13 +7,13 @@
 //!
 //! Each module has a focused job:
 //!
-//! | Module          | Layer   | Job                                                      |
-//! |-----------------|---------|----------------------------------------------------------|
-//! | `editor`        | Entity  | Owns text, cursor, blink task, `EntityInputHandler`      |
-//! | `editor_text`   | Element | Shapes text, paints cursor, wires `handle_input`         |
-//! | `input`         | View    | Single-line input โ€” composes `ExampleEditorText` with styling   |
-//! | `text_area`     | View    | Multi-line text area โ€” same entity, different layout      |
-//! | `main` (here)   | Render  | Root view โ€” creates entities with `use_state`, assembles  |
+//! | Module          | Trait         | Job                                                    |
+//! |-----------------|---------------|--------------------------------------------------------|
+//! | `editor`        | EntityView    | Owns text, cursor, blink; renders via ExampleEditorText |
+//! | `input`         | View          | Single-line input with own state + cached editor child  |
+//! | `editor_info`   | View          | Read-only stats display; zero-wiring, same editor entity |
+//! | `text_area`     | ComponentView | Stateless multi-line wrapper; inner editor caches       |
+//! | `main` (here)   | Render        | Root view; creates entities with `use_state`, assembles  |
 //!
 //! ## Running
 //!
@@ -28,6 +28,7 @@
 //! ```
 
 mod example_editor;
+mod example_editor_info;
 mod example_input;
 mod example_text_area;
 
@@ -41,6 +42,7 @@ use gpui::{
 use gpui_platform::application;
 
 use example_editor::ExampleEditor;
+use example_editor_info::EditorInfo;
 use example_input::{ExampleInput, ExampleInputState};
 use example_text_area::ExampleTextArea;
 
@@ -70,6 +72,7 @@ impl ViewExample {
 impl Render for ViewExample {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let input_state = window.use_state(cx, |window, cx| ExampleInputState::new(window, cx));
+        let input_editor = input_state.read(cx).editor.clone();
         let textarea_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx));
         let input_color = self.input_color;
         let textarea_color = self.textarea_color;
@@ -87,15 +90,17 @@ impl Render for ViewExample {
                     .flex_col()
                     .gap(px(4.))
                     .child(
-                        div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
-                            "Single-line input (Input โ€” View with own state + cached editor)",
-                        ),
+                        div()
+                            .text_sm()
+                            .text_color(hsla(0., 0., 0.3, 1.))
+                            .child("Single-line input (View with own state + cached editor)"),
                     )
                     .child(
                         ExampleInput::new(input_state)
                             .width(px(320.))
                             .color(input_color),
-                    ),
+                    )
+                    .child(EditorInfo::new(input_editor)),
             )
             .child(
                 div()
@@ -115,13 +120,11 @@ impl Render for ViewExample {
                     .mt(px(12.))
                     .text_xs()
                     .text_color(hsla(0., 0., 0.5, 1.))
-                    .child("โ€ข ExampleEditor entity owns text, cursor, blink (EntityView)")
-                    .child("โ€ข ExampleInput is a View with its own state โ€” caches independently")
-                    .child(
-                        "โ€ข ExampleTextArea is a ComponentView โ€” stateless wrapper, editor caches",
-                    )
-                    .child("โ€ข Press Enter in input to flash border (only chrome re-renders)")
-                    .child("โ€ข Entities created via window.use_state()"),
+                    .child("โ€ข ExampleInput: View with own state โ€” caches independently")
+                    .child("โ€ข EditorInfo: View on same editor โ€” zero-wiring, auto-cached")
+                    .child("โ€ข ExampleTextArea: ComponentView โ€” stateless wrapper")
+                    .child("โ€ข Press Enter in input to flash border (EditorInfo stays cached)")
+                    .child("โ€ข Type to see both input and info update reactively"),
             )
     }
 }