Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:12 +08:00
commit 1ab103caa3
16 changed files with 6782 additions and 0 deletions

593
commands/gpui-component.md Normal file
View File

@@ -0,0 +1,593 @@
---
name: gpui-component
description: Generate reusable GPUI components with proper typing, state management, styling, and documentation
---
# GPUI Component Generator
Generate reusable, well-structured GPUI components with proper typing, state management, styling, and comprehensive documentation.
## Arguments
- `$1`: Component name (required) - Name of the component in PascalCase (e.g., "Button", "DataTable", "SearchInput")
- `$2`: Component type (optional) - Either "stateless" (default) or "stateful"
## Workflow
### 1. Gather Component Requirements
Ask user for:
- Component purpose and description
- Props/configuration needed
- Whether component needs internal state
- Event handlers required
- Styling requirements
- Accessibility needs
### 2. Generate Component Struct
Create component struct based on type:
#### Stateless Component
```rust
use gpui::*;
/// A reusable button component
///
/// # Examples
///
/// ```
/// Button::new("Click me")
/// .on_click(|cx| {
/// println!("Clicked!");
/// })
/// ```
pub struct Button {
label: String,
variant: ButtonVariant,
disabled: bool,
on_click: Option<Box<dyn Fn(&mut WindowContext)>>,
}
#[derive(Clone, Copy, PartialEq)]
pub enum ButtonVariant {
Primary,
Secondary,
Destructive,
Ghost,
}
impl Button {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
variant: ButtonVariant::Primary,
disabled: false,
on_click: None,
}
}
pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = variant;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_click(mut self, handler: impl Fn(&mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
```
#### Stateful Component
```rust
use gpui::*;
/// A search input with autocomplete
pub struct SearchInput {
query: String,
suggestions: Vec<String>,
selected_index: Option<usize>,
on_search: Option<Box<dyn Fn(&str, &mut WindowContext)>>,
}
impl SearchInput {
pub fn new() -> Self {
Self {
query: String::new(),
suggestions: Vec::new(),
selected_index: None,
on_search: None,
}
}
pub fn on_search(mut self, handler: impl Fn(&str, &mut WindowContext) + 'static) -> Self {
self.on_search = Some(Box::new(handler));
self
}
fn handle_input(&mut self, value: String, cx: &mut ViewContext<Self>) {
self.query = value;
self.update_suggestions(cx);
cx.notify();
}
fn update_suggestions(&mut self, cx: &mut ViewContext<Self>) {
// Update suggestions based on query
if let Some(handler) = &self.on_search {
handler(&self.query, cx);
}
}
fn handle_key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
match event.key.as_str() {
"ArrowDown" => {
self.selected_index = Some(
self.selected_index
.map(|i| (i + 1).min(self.suggestions.len() - 1))
.unwrap_or(0)
);
cx.notify();
}
"ArrowUp" => {
self.selected_index = self.selected_index
.and_then(|i| i.checked_sub(1));
cx.notify();
}
"Enter" => {
if let Some(index) = self.selected_index {
self.query = self.suggestions[index].clone();
self.suggestions.clear();
cx.notify();
}
}
_ => {}
}
}
}
```
### 3. Implement Element/Render Traits
#### Stateless Component Render
```rust
impl Render for Button {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
let (bg_color, text_color, hover_color) = match self.variant {
ButtonVariant::Primary => (
theme.primary,
theme.primary_foreground,
theme.primary_hover,
),
ButtonVariant::Secondary => (
theme.secondary,
theme.secondary_foreground,
theme.secondary_hover,
),
ButtonVariant::Destructive => (
theme.destructive,
theme.destructive_foreground,
theme.destructive_hover,
),
ButtonVariant::Ghost => (
hsla(0.0, 0.0, 0.0, 0.0),
theme.foreground,
theme.muted,
),
};
div()
.px_4()
.py_2()
.bg(bg_color)
.text_color(text_color)
.rounded_md()
.font_medium()
.when(!self.disabled, |this| {
this.cursor_pointer()
.hover(|this| this.bg(hover_color))
})
.when(self.disabled, |this| {
this.opacity(0.5)
.cursor_not_allowed()
})
.when_some(self.on_click.take(), |this, handler| {
this.on_click(move |_, cx| {
if !self.disabled {
handler(cx);
}
})
})
.child(self.label.clone())
}
}
```
#### Stateful Component Render
```rust
impl Render for SearchInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
div()
.flex()
.flex_col()
.relative()
.child(
div()
.flex()
.items_center()
.px_3()
.py_2()
.bg(theme.background)
.border_1()
.border_color(theme.border)
.rounded_md()
.child(
input()
.flex_1()
.placeholder("Search...")
.value(&self.query)
.on_input(cx.listener(|this, value, cx| {
this.handle_input(value, cx);
}))
.on_key_down(cx.listener(|this, event, cx| {
this.handle_key_down(event, cx);
}))
)
)
.when(!self.suggestions.is_empty(), |this| {
this.child(
div()
.absolute()
.top_full()
.left_0()
.right_0()
.mt_1()
.bg(theme.background)
.border_1()
.border_color(theme.border)
.rounded_md()
.shadow_lg()
.max_h_64()
.overflow_y_auto()
.children(
self.suggestions.iter().enumerate().map(|(i, suggestion)| {
div()
.px_3()
.py_2()
.cursor_pointer()
.when(self.selected_index == Some(i), |this| {
this.bg(theme.accent)
})
.hover(|this| this.bg(theme.muted))
.child(suggestion.as_str())
})
)
)
})
}
}
```
### 4. Add State Management
For stateful components:
```rust
impl SearchInput {
pub fn set_suggestions(&mut self, suggestions: Vec<String>, cx: &mut ViewContext<Self>) {
self.suggestions = suggestions;
self.selected_index = None;
cx.notify();
}
pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
self.query.clear();
self.suggestions.clear();
self.selected_index = None;
cx.notify();
}
pub fn query(&self) -> &str {
&self.query
}
}
```
### 5. Generate Styling
Create styled variants and theme integration:
```rust
// Component-specific theme
pub struct ButtonTheme {
pub primary_bg: Hsla,
pub primary_fg: Hsla,
pub primary_hover: Hsla,
pub secondary_bg: Hsla,
pub secondary_fg: Hsla,
pub secondary_hover: Hsla,
pub border_radius: Pixels,
pub padding_x: Pixels,
pub padding_y: Pixels,
}
impl ButtonTheme {
pub fn from_app_theme(theme: &AppTheme) -> Self {
Self {
primary_bg: theme.colors.primary,
primary_fg: theme.colors.primary_foreground,
primary_hover: theme.colors.primary_hover,
secondary_bg: theme.colors.secondary,
secondary_fg: theme.colors.secondary_foreground,
secondary_hover: theme.colors.secondary_hover,
border_radius: px(6.0),
padding_x: px(16.0),
padding_y: px(8.0),
}
}
}
```
### 6. Create Documentation
Generate comprehensive documentation:
```rust
//! Button Component
//!
//! A flexible, accessible button component with multiple variants and states.
//!
//! # Features
//!
//! - Multiple variants (Primary, Secondary, Destructive, Ghost)
//! - Disabled state support
//! - Customizable click handlers
//! - Full keyboard accessibility
//! - Theme integration
//!
//! # Examples
//!
//! ## Basic Usage
//!
//! ```rust
//! let button = Button::new("Click me")
//! .on_click(|cx| {
//! println!("Button clicked!");
//! });
//! ```
//!
//! ## With Variants
//!
//! ```rust
//! let primary = Button::new("Primary").variant(ButtonVariant::Primary);
//! let secondary = Button::new("Secondary").variant(ButtonVariant::Secondary);
//! let destructive = Button::new("Delete").variant(ButtonVariant::Destructive);
//! ```
//!
//! ## Disabled State
//!
//! ```rust
//! let button = Button::new("Disabled")
//! .disabled(true);
//! ```
//!
//! # Accessibility
//!
//! - Supports keyboard navigation (Enter/Space to activate)
//! - Proper ARIA attributes
//! - Focus indicators
//! - Disabled state communicated to screen readers
```
### 7. Provide Usage Examples
Create example usage code:
```rust
// examples/button_example.rs
use gpui::*;
fn main() {
App::new("com.example.button-demo", |cx| {
cx.open_window(
WindowOptions::default(),
|cx| cx.new_view(|cx| ButtonDemo::new(cx))
)
}).run();
}
struct ButtonDemo;
impl ButtonDemo {
fn new(cx: &mut ViewContext<Self>) -> Self {
Self
}
}
impl Render for ButtonDemo {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.gap_4()
.p_8()
.child(
Button::new("Primary Button")
.variant(ButtonVariant::Primary)
.on_click(|cx| {
println!("Primary clicked!");
})
)
.child(
Button::new("Secondary Button")
.variant(ButtonVariant::Secondary)
.on_click(|cx| {
println!("Secondary clicked!");
})
)
.child(
Button::new("Destructive Button")
.variant(ButtonVariant::Destructive)
.on_click(|cx| {
println!("Destructive clicked!");
})
)
.child(
Button::new("Disabled Button")
.disabled(true)
)
}
}
```
### 8. Add Component Tests
Generate tests for the component:
```rust
#[cfg(test)]
mod tests {
use super::*;
#[gpui::test]
fn test_button_creation() {
App::test(|cx| {
let button = Button::new("Test");
assert_eq!(button.label, "Test");
assert_eq!(button.variant, ButtonVariant::Primary);
assert!(!button.disabled);
});
}
#[gpui::test]
fn test_button_click() {
App::test(|cx| {
let clicked = Rc::new(RefCell::new(false));
let clicked_clone = clicked.clone();
let button = Button::new("Test")
.on_click(move |_| {
*clicked_clone.borrow_mut() = true;
});
// Simulate click
if let Some(handler) = button.on_click {
handler(cx);
}
assert!(*clicked.borrow());
});
}
}
```
### 9. Generate Component Module
Create module file with exports:
```rust
// src/ui/components/button/mod.rs
mod button;
mod theme;
pub use button::{Button, ButtonVariant};
pub use theme::ButtonTheme;
```
### 10. Provide Integration Instructions
Output integration guide:
```
✓ Created Button component
Files created:
- src/ui/components/button/button.rs
- src/ui/components/button/theme.rs
- src/ui/components/button/mod.rs
- examples/button_example.rs
- tests/button_test.rs
Next steps:
1. Add to your components module:
In src/ui/components/mod.rs:
pub mod button;
pub use button::Button;
2. Use in your views:
use crate::ui::components::Button;
Button::new("Click me")
.variant(ButtonVariant::Primary)
.on_click(|cx| {
// Handle click
})
3. Run example:
cargo run --example button_example
4. Run tests:
cargo test button
Documentation: See generated component docs for full API
```
## Component Types
### Stateless Components
- No internal state
- Pure rendering based on props
- Examples: Button, Icon, Label
### Stateful Components
- Internal state management
- User input handling
- Examples: Input, SearchBox, Dropdown
### Container Components
- Manage child components
- State coordination
- Examples: Form, List, Tabs
### Composite Components
- Combine multiple components
- Complex functionality
- Examples: DataTable, Dialog, Wizard
## Best Practices
- Builder pattern for configuration
- Theme integration
- Accessibility attributes
- Comprehensive documentation
- Usage examples
- Unit tests
- Type safety
- Error handling
## Example Usage
```bash
# Generate stateless component
/gpui-component Button
# Generate stateful component
/gpui-component SearchInput stateful
# Generate with custom requirements
/gpui-component DataTable stateful --with-examples --with-tests
```

361
commands/gpui-review.md Normal file
View File

@@ -0,0 +1,361 @@
---
name: gpui-review
description: Review GPUI code for idiomatic patterns, performance issues, state management correctness, and framework best practices
---
# GPUI Code Review
Perform comprehensive code review of GPUI applications, identifying issues with patterns, performance, state management, and suggesting improvements.
## Arguments
- `$1`: Path (optional) - Specific file or directory to review. If not provided, reviews entire project.
## Workflow
### 1. Search for GPUI Code
- Locate all `.rs` files in the project or specified path
- Identify files using GPUI (imports `gpui::*` or specific GPUI types)
- Categorize files by type:
- View components (impl Render)
- Models (application state)
- Main entry points
- Utility code
### 2. Analyze Component Patterns
Review each component for:
#### Component Structure
- [ ] Proper use of View vs Model types
- [ ] Clear separation of concerns
- [ ] Component has single responsibility
- [ ] Props/dependencies passed explicitly
- [ ] Proper lifetime management
#### Anti-patterns
- [ ] God components (doing too much)
- [ ] Tight coupling between components
- [ ] Improper state ownership
- [ ] Missing error handling
- [ ] Inconsistent naming conventions
Example issues to flag:
```rust
// BAD: Component doing too much
impl Render for GodComponent {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// Fetching data
// Computing business logic
// Rendering UI
// Handling all events
// Managing multiple concerns
}
}
// GOOD: Focused component
impl Render for FocusedComponent {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.model.read(cx);
div().child(format!("Count: {}", state.count))
}
}
```
### 3. Check State Management
Review state management for:
#### Model Usage
- [ ] State properly encapsulated in Model types
- [ ] Appropriate use of `cx.new_model()`
- [ ] State updates use `model.update()`
- [ ] No direct state mutation outside updates
- [ ] Proper state ownership hierarchy
#### Subscription Management
- [ ] Subscriptions created during initialization, not render
- [ ] Subscriptions stored to prevent cleanup
- [ ] `cx.observe()` used for model changes
- [ ] No subscription leaks
- [ ] Proper cleanup in Drop if needed
Example issues to flag:
```rust
// BAD: Subscription in render (memory leak)
impl Render for BadView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
cx.observe(&self.model, |_, _, cx| cx.notify()); // Leak!
div()
}
}
// GOOD: Subscription stored
struct GoodView {
model: Model<Data>,
_subscription: Subscription,
}
impl GoodView {
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| cx.notify());
Self { model, _subscription }
}
}
```
#### Context Usage
- [ ] Appropriate context types (WindowContext, ViewContext, ModelContext)
- [ ] Global state used sparingly with `cx.global::<T>()`
- [ ] Context not stored (lifetime issues)
- [ ] Proper use of `cx.notify()` for updates
### 4. Review Render Performance
Analyze for performance issues:
#### Unnecessary Renders
- [ ] No expensive computations in render()
- [ ] Cached/memoized expensive operations
- [ ] Minimal `cx.notify()` calls
- [ ] No rendering on every tick/timer
- [ ] Proper use of derived state
#### Element Efficiency
- [ ] Minimal element nesting depth
- [ ] No repeated identical elements
- [ ] Efficient list rendering (consider virtualization)
- [ ] Appropriate use of keys for dynamic lists
- [ ] No unnecessary cloning in render
Example issues to flag:
```rust
// BAD: Expensive computation in render
impl Render for BadComponent {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let result = expensive_computation(); // Every render!
div().child(result)
}
}
// GOOD: Compute on state change
struct GoodComponent {
cached_result: String,
}
impl GoodComponent {
fn update_data(&mut self, data: Data, cx: &mut ViewContext<Self>) {
self.cached_result = expensive_computation(&data);
cx.notify();
}
}
```
#### Layout Performance
- [ ] Avoid deep nesting
- [ ] Use flex layout efficiently
- [ ] Minimize layout recalculations
- [ ] Appropriate use of fixed vs dynamic sizing
- [ ] No layout thrashing
### 5. Identify Anti-Patterns
Flag common GPUI anti-patterns:
#### Memory Issues
```rust
// BAD: Circular reference
struct CircularRef {
self_view: Option<View<Self>>, // Circular!
}
// BAD: Unbounded growth
struct UnboundedList {
items: Vec<Item>, // Grows forever
}
```
#### Incorrect Context Usage
```rust
// BAD: Storing context
struct BadComponent {
cx: ViewContext<Self>, // Won't compile, lifetime issues
}
// GOOD: Use context in methods
impl BadComponent {
fn do_something(&mut self, cx: &mut ViewContext<Self>) {
// Use cx here
}
}
```
#### Event Handling Issues
```rust
// BAD: Not preventing default when needed
div()
.on_click(|_, cx| {
// Action taken but event still propagates
})
// GOOD: Prevent propagation when appropriate
div()
.on_click(|event, cx| {
event.stop_propagation();
// Action taken
})
```
### 6. Provide Actionable Suggestions
For each issue found, provide:
1. **Location**: File and line number
2. **Issue**: Clear description of the problem
3. **Why**: Explain why it's problematic
4. **Fix**: Show concrete code example of how to fix it
5. **Priority**: Critical, High, Medium, or Low
Example output format:
```
📁 src/ui/views/main_view.rs:45
❌ Issue: Subscription created in render method
Severity: Critical (Memory Leak)
Problem:
cx.observe(&self.model, |_, _, cx| cx.notify());
This creates a new subscription every render, causing a memory leak.
Subscriptions are never cleaned up.
Fix:
1. Add subscription field to struct:
struct MainView {
model: Model<Data>,
_subscription: Subscription,
}
2. Create subscription in constructor:
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| cx.notify());
Self { model, _subscription }
}
```
### 7. Check Framework Best Practices
Review for GPUI best practices:
- [ ] Proper use of Element trait
- [ ] Appropriate render trait implementations
- [ ] Correct action system usage
- [ ] Proper theme integration
- [ ] Accessibility attributes where appropriate
- [ ] Error handling and user feedback
- [ ] Type safety leveraged
- [ ] Documentation for public APIs
### 8. Generate Summary Report
Provide summary including:
- Total files reviewed
- Issues found by severity (Critical, High, Medium, Low)
- Common patterns identified
- Overall code quality assessment
- Top priority fixes
- Positive patterns to highlight
Example summary:
```
GPUI Code Review Summary
========================
Files Reviewed: 15
Total Issues: 23
By Severity:
Critical: 2 (Memory leaks, state corruption)
High: 5 (Performance issues, anti-patterns)
Medium: 10 (Code organization, minor inefficiencies)
Low: 6 (Style, documentation)
Top Priority Fixes:
1. Fix subscription leaks in MainView and SidebarView
2. Move expensive computations out of render methods
3. Implement proper state ownership hierarchy
4. Add error handling for async operations
5. Reduce component nesting depth in ComplexView
Positive Patterns:
✓ Good separation between UI and business logic
✓ Consistent theming throughout
✓ Proper use of Model types for state
✓ Good component composition in most areas
Recommendations:
- Consider extracting reusable components from large views
- Implement virtual scrolling for long lists
- Add integration tests for critical user flows
- Document component APIs and state flow
```
## Review Categories
### Architecture
- Project structure
- Module organization
- Dependency management
- Separation of concerns
### State Management
- Model usage
- Subscription patterns
- Context usage
- State ownership
### Performance
- Render efficiency
- Layout optimization
- Memory management
- Async operations
### Code Quality
- Idiomatic Rust
- Error handling
- Type safety
- Documentation
### UI/UX
- Component reusability
- Consistent styling
- Accessibility
- User feedback
## Example Usage
```bash
# Review entire project
/gpui-review
# Review specific file
/gpui-review src/ui/views/main_view.rs
# Review directory
/gpui-review src/ui/components/
```
## Notes
- Focuses on GPUI-specific patterns and idioms
- Considers both correctness and performance
- Provides educational feedback with explanations
- Prioritizes actionable, concrete suggestions
- Highlights both issues and good patterns

376
commands/gpui-scaffold.md Normal file
View File

@@ -0,0 +1,376 @@
---
name: gpui-scaffold
description: Scaffold new GPUI applications with modern structure, Cargo workspace setup, component organization, and best practices
---
# GPUI Project Scaffolding
Scaffold a new GPUI application with modern project structure, best practices, and example components.
## Arguments
- `$1`: Project name (required) - Name for the new GPUI project (hyphen-case recommended)
- `$2`: Template type (optional) - Either "app" (default) or "library"
## Workflow
### 1. Validate Project Name
- Check that project name is provided
- Validate naming convention (lowercase, hyphens, no spaces)
- Check that directory doesn't already exist
- Confirm project creation with user if needed
### 2. Create Directory Structure
Create the following structure:
```
project-name/
├── Cargo.toml
├── .gitignore
├── README.md
├── src/
│ ├── main.rs (for app) or lib.rs (for library)
│ ├── app.rs
│ ├── ui/
│ │ ├── mod.rs
│ │ ├── views/
│ │ │ ├── mod.rs
│ │ │ └── main_view.rs
│ │ ├── components/
│ │ │ ├── mod.rs
│ │ │ ├── button.rs
│ │ │ └── input.rs
│ │ └── theme.rs
│ ├── models/
│ │ ├── mod.rs
│ │ └── app_state.rs
│ └── utils/
│ └── mod.rs
├── examples/
│ └── basic.rs
└── tests/
└── integration_test.rs
```
### 3. Generate Cargo.toml
Create `Cargo.toml` with:
```toml
[package]
name = "project-name"
version = "0.1.0"
edition = "2021"
[dependencies]
gpui = { git = "https://github.com/zed-industries/zed" }
anyhow = "1.0"
log = "0.4"
env_logger = "0.11"
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "rendering"
harness = false
```
### 4. Create Main Entry Point
For applications (`main.rs`):
```rust
use gpui::*;
mod app;
mod ui;
mod models;
mod utils;
use app::App;
fn main() {
env_logger::init();
App::new("com.example.project-name", |cx| {
let app = cx.new_model(|cx| app::AppModel::new(cx));
let window = cx.open_window(
WindowOptions {
bounds: WindowBounds::Fixed(Bounds {
origin: point(px(100.0), px(100.0)),
size: size(px(1200.0), px(800.0)),
}),
..Default::default()
},
|cx| cx.new_view(|cx| ui::views::MainView::new(app.clone(), cx)),
);
window.unwrap()
})
.run();
}
```
For libraries (`lib.rs`):
```rust
pub mod ui;
pub mod models;
pub mod utils;
pub use ui::*;
pub use models::*;
```
### 5. Create App Model
Generate `src/app.rs`:
```rust
use gpui::*;
use crate::models::AppState;
pub struct AppModel {
state: Model<AppState>,
}
impl AppModel {
pub fn new(cx: &mut ModelContext<Self>) -> Self {
Self {
state: cx.new_model(|_| AppState::default()),
}
}
pub fn state(&self) -> &Model<AppState> {
&self.state
}
}
```
### 6. Create Component Structure
Generate main view (`src/ui/views/main_view.rs`):
```rust
use gpui::*;
use crate::app::AppModel;
use crate::ui::components::{Button, Input};
pub struct MainView {
app: Model<AppModel>,
_subscription: Subscription,
}
impl MainView {
pub fn new(app: Model<AppModel>, cx: &mut ViewContext<Self>) -> Self {
let subscription = cx.observe(&app, |_, _, cx| cx.notify());
Self {
app,
_subscription: subscription,
}
}
}
impl Render for MainView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
div()
.flex()
.flex_col()
.size_full()
.bg(theme.background)
.child(
div()
.flex()
.items_center()
.justify_center()
.flex_1()
.child("Welcome to your GPUI app!")
)
}
}
```
Generate reusable components (`src/ui/components/button.rs`, `src/ui/components/input.rs`).
### 7. Add Example Components
Create example button component:
```rust
use gpui::*;
pub struct Button {
label: String,
on_click: Option<Box<dyn Fn(&mut WindowContext)>>,
}
impl Button {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
on_click: None,
}
}
pub fn on_click(mut self, handler: impl Fn(&mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl Render for Button {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
let on_click = self.on_click.take();
div()
.px_4()
.py_2()
.bg(theme.primary)
.text_color(theme.primary_foreground)
.rounded_md()
.cursor_pointer()
.hover(|style| style.bg(theme.primary_hover))
.when_some(on_click, |this, handler| {
this.on_click(move |_, cx| handler(cx))
})
.child(self.label.clone())
}
}
```
### 8. Generate Theme System
Create `src/ui/theme.rs`:
```rust
use gpui::*;
#[derive(Clone)]
pub struct Theme {
pub background: Hsla,
pub foreground: Hsla,
pub primary: Hsla,
pub primary_foreground: Hsla,
pub primary_hover: Hsla,
pub border: Hsla,
}
impl Default for Theme {
fn default() -> Self {
Self::light()
}
}
impl Theme {
pub fn light() -> Self {
Self {
background: rgb(0xffffff),
foreground: rgb(0x000000),
primary: rgb(0x2563eb),
primary_foreground: rgb(0xffffff),
primary_hover: rgb(0x1d4ed8),
border: rgb(0xe5e7eb),
}
}
pub fn dark() -> Self {
Self {
background: rgb(0x1f2937),
foreground: rgb(0xf9fafb),
primary: rgb(0x3b82f6),
primary_foreground: rgb(0xffffff),
primary_hover: rgb(0x2563eb),
border: rgb(0x374151),
}
}
}
```
### 9. Generate README
Create comprehensive README.md with:
- Project description
- Installation instructions
- Usage examples
- Development setup
- Building and running instructions
- Testing guidelines
- Contributing information
### 10. Add .gitignore
```
/target/
Cargo.lock
*.swp
*.swo
.DS_Store
```
### 11. Initialize Git Repository (Optional)
- Run `git init`
- Create initial commit
- Ask user if they want to push to remote
### 12. Provide Next Steps
Output guidance:
```
✓ Created GPUI project: project-name
Next steps:
cd project-name
cargo build
cargo run
Project structure:
- src/main.rs: Application entry point
- src/app.rs: Application model
- src/ui/: UI components and views
- src/models/: Application state models
- src/utils/: Utility functions
To add components:
- Create new files in src/ui/components/
- Add to src/ui/components/mod.rs
- Use in your views
Documentation:
- GPUI: https://github.com/zed-industries/zed/tree/main/crates/gpui
- Examples: See examples/ directory
```
## Best Practices Included
- Modern project structure with clear separation of concerns
- Theme system for consistent styling
- Reusable component patterns
- Example components to get started
- Proper subscription management
- Type-safe state management
- Development tools (examples, tests)
## Example Usage
```bash
# Scaffold new application
/gpui-scaffold my-gpui-app
# Scaffold library project
/gpui-scaffold my-gpui-lib library
```
## Notes
- Uses latest GPUI from git (stable API)
- Follows Rust 2021 edition conventions
- Includes development dependencies for testing
- Sets up proper module structure
- Includes example code for common patterns

491
commands/gpui-test.md Normal file
View File

@@ -0,0 +1,491 @@
---
name: gpui-test
description: Generate comprehensive tests for GPUI components, views, state management, and user interactions
---
# GPUI Test Generation
Generate comprehensive tests for GPUI components including unit tests, integration tests, state management tests, and user interaction tests.
## Arguments
- `$1`: Component path (required) - Path to the component file to generate tests for
## Workflow
### 1. Analyze Component Structure
- Read the component file
- Identify component type (View, Model, Element)
- Extract component struct and fields
- Identify render method and UI structure
- Find state management patterns
- Locate event handlers and actions
- Identify dependencies and injected services
### 2. Generate Unit Tests
Create unit tests for component logic:
#### Component Initialization Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
use gpui::*;
#[gpui::test]
fn test_component_initialization() {
App::test(|cx| {
let state = cx.new_model(|_| AppState::default());
let view = cx.new_view(|cx| MyComponent::new(state.clone(), cx));
assert!(view.is_some());
});
}
#[gpui::test]
fn test_initial_state() {
App::test(|cx| {
let state = cx.new_model(|_| AppState {
count: 0,
items: vec![],
});
let view = cx.new_view(|cx| MyComponent::new(state.clone(), cx));
view.update(cx, |view, cx| {
let state = view.state.read(cx);
assert_eq!(state.count, 0);
assert_eq!(state.items.len(), 0);
});
});
}
}
```
#### State Management Tests
```rust
#[gpui::test]
fn test_state_updates() {
App::test(|cx| {
let state = cx.new_model(|_| AppState { count: 0 });
let view = cx.new_view(|cx| Counter::new(state.clone(), cx));
// Update state
state.update(cx, |state, cx| {
state.count = 5;
cx.notify();
});
// Verify view reflects change
view.update(cx, |view, cx| {
let state = view.state.read(cx);
assert_eq!(state.count, 5);
});
});
}
#[gpui::test]
fn test_subscription_updates() {
App::test(|cx| {
let state = cx.new_model(|_| AppState { count: 0 });
let view = cx.new_view(|cx| Counter::new(state.clone(), cx));
let initial_render_count = view.render_count();
// Update should trigger rerender via subscription
state.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
assert_eq!(view.render_count(), initial_render_count + 1);
});
}
```
### 3. Create Integration Tests
Generate integration tests for component interactions:
#### User Interaction Tests
```rust
#[gpui::test]
fn test_button_click() {
App::test(|cx| {
let state = cx.new_model(|_| AppState { count: 0 });
let view = cx.new_view(|cx| Counter::new(state.clone(), cx));
// Simulate button click
view.update(cx, |view, cx| {
view.handle_increment(cx);
});
// Verify state updated
state.update(cx, |state, _| {
assert_eq!(state.count, 1);
});
});
}
#[gpui::test]
fn test_input_change() {
App::test(|cx| {
let state = cx.new_model(|_| FormState::default());
let view = cx.new_view(|cx| Form::new(state.clone(), cx));
// Simulate input change
view.update(cx, |view, cx| {
view.handle_input_change("test value", cx);
});
// Verify state updated
state.update(cx, |state, _| {
assert_eq!(state.input_value, "test value");
});
});
}
```
#### Action Handling Tests
```rust
#[gpui::test]
fn test_action_dispatch() {
App::test(|cx| {
let state = cx.new_model(|_| AppState { count: 0 });
let view = cx.new_view(|cx| Counter::new(state.clone(), cx));
// Dispatch action
view.update(cx, |view, cx| {
cx.dispatch_action(Increment);
});
// Verify action handled
state.update(cx, |state, _| {
assert_eq!(state.count, 1);
});
});
}
```
### 4. Add Interaction Tests
Test complex user interactions:
#### Multi-Step Interactions
```rust
#[gpui::test]
fn test_complete_user_flow() {
App::test(|cx| {
let state = cx.new_model(|_| TodoState::default());
let view = cx.new_view(|cx| TodoList::new(state.clone(), cx));
view.update(cx, |view, cx| {
// Add item
view.handle_add_todo("Buy milk", cx);
// Mark complete
view.handle_toggle_todo(0, cx);
// Delete item
view.handle_delete_todo(0, cx);
});
state.update(cx, |state, _| {
assert_eq!(state.todos.len(), 0);
});
});
}
```
#### Edge Cases
```rust
#[gpui::test]
fn test_empty_state() {
App::test(|cx| {
let state = cx.new_model(|_| AppState::default());
let view = cx.new_view(|cx| MyComponent::new(state.clone(), cx));
// Verify graceful handling of empty state
view.update(cx, |view, cx| {
let element = view.render(cx);
// Assert renders without panic
});
});
}
#[gpui::test]
fn test_boundary_conditions() {
App::test(|cx| {
let state = cx.new_model(|_| CounterState { count: i32::MAX });
let view = cx.new_view(|cx| Counter::new(state.clone(), cx));
// Test overflow handling
view.update(cx, |view, cx| {
view.handle_increment(cx);
});
state.update(cx, |state, _| {
// Should handle overflow gracefully
assert!(state.count == i32::MAX || state.count == 0);
});
});
}
```
### 5. Generate Test Utilities
Create helper functions for testing:
```rust
// Test helpers
mod test_utils {
use super::*;
use gpui::*;
pub fn create_test_state() -> AppState {
AppState {
count: 0,
items: vec!["item1".to_string(), "item2".to_string()],
is_loading: false,
}
}
pub fn create_test_view(cx: &mut WindowContext) -> View<MyComponent> {
let state = cx.new_model(|_| create_test_state());
cx.new_view(|cx| MyComponent::new(state, cx))
}
pub fn assert_state_equals(state: &Model<AppState>, expected_count: i32, cx: &mut AppContext) {
state.update(cx, |state, _| {
assert_eq!(state.count, expected_count);
});
}
}
```
### 6. Provide Coverage Report
Generate overview of test coverage:
```rust
// Coverage targets:
// - Component initialization: ✓
// - State updates: ✓
// - User interactions: ✓
// - Action handling: ✓
// - Edge cases: ✓
// - Error handling: ⚠ (needs work)
// - Async operations: ⚠ (needs work)
```
### 7. Add Async Tests
For components with async operations:
```rust
#[gpui::test]
async fn test_async_data_loading() {
App::test(|cx| async move {
let state = cx.new_model(|_| DataState::default());
let view = cx.new_view(|cx| DataView::new(state.clone(), cx));
// Trigger async load
view.update(cx, |view, cx| {
view.load_data(cx);
});
// Wait for completion
cx.run_until_parked();
// Verify data loaded
state.update(cx, |state, _| {
assert!(state.is_loaded);
assert!(!state.data.is_empty());
});
});
}
#[gpui::test]
async fn test_async_error_handling() {
App::test(|cx| async move {
let state = cx.new_model(|_| DataState::default());
let view = cx.new_view(|cx| DataView::new(state.clone(), cx));
// Trigger async operation that will fail
view.update(cx, |view, cx| {
view.load_data_with_error(cx);
});
cx.run_until_parked();
// Verify error handled
state.update(cx, |state, _| {
assert!(state.error.is_some());
assert!(!state.is_loaded);
});
});
}
```
### 8. Generate Property-Based Tests
For complex logic, generate property-based tests:
```rust
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_counter_never_negative(increments in 0..100u32, decrements in 0..100u32) {
App::test(|cx| {
let state = cx.new_model(|_| CounterState { count: 0 });
state.update(cx, |state, _| {
for _ in 0..increments {
state.count += 1;
}
for _ in 0..decrements {
state.count = state.count.saturating_sub(1);
}
});
state.update(cx, |state, _| {
prop_assert!(state.count >= 0);
Ok(())
}).unwrap();
});
}
}
}
```
### 9. Add Benchmark Tests
Create performance benchmarks:
```rust
// benches/component_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn render_benchmark(c: &mut Criterion) {
c.bench_function("component render", |b| {
App::test(|cx| {
let state = cx.new_model(|_| create_large_state());
let view = cx.new_view(|cx| MyComponent::new(state, cx));
b.iter(|| {
view.update(cx, |view, cx| {
black_box(view.render(cx));
});
});
});
});
}
criterion_group!(benches, render_benchmark);
criterion_main!(benches);
```
### 10. Generate Test Documentation
Create documentation for tests:
```rust
//! Component Tests
//!
//! This module contains comprehensive tests for MyComponent including:
//!
//! - Unit tests for component logic and state management
//! - Integration tests for user interactions
//! - Async tests for data loading
//! - Property-based tests for invariants
//! - Performance benchmarks
//!
//! ## Running Tests
//!
//! ```bash
//! # Run all tests
//! cargo test
//!
//! # Run specific test
//! cargo test test_state_updates
//!
//! # Run with output
//! cargo test -- --nocapture
//!
//! # Run benchmarks
//! cargo bench
//! ```
```
## Test Categories
### Unit Tests
- Component initialization
- State management
- Helper functions
- Pure logic
### Integration Tests
- User interactions
- Component composition
- Event propagation
- Action handling
### UI Tests
- Render output
- Layout calculations
- Style application
- Theme integration
### Performance Tests
- Render benchmarks
- State update performance
- Memory usage
- Subscription efficiency
## Example Usage
```bash
# Generate tests for specific component
/gpui-test src/ui/views/counter.rs
# Generate tests for all components in directory
/gpui-test src/ui/components/
# Generate tests with benchmarks
/gpui-test src/ui/views/data_view.rs --with-benchmarks
```
## Best Practices
- Test behavior, not implementation
- Use descriptive test names
- Test edge cases and error conditions
- Keep tests focused and independent
- Use test utilities for common setup
- Document complex test scenarios
- Maintain high coverage (>80%)
- Run tests in CI/CD pipeline
## Output Structure
```
tests/
├── component_name_test.rs
│ ├── Unit tests
│ ├── Integration tests
│ └── Test utilities
└── benches/
└── component_name_bench.rs
```